Compare commits

..
79 Commits
Author SHA1 Message Date
Jones 4343c0e0be Release 2.1.34 (#488) 2025-07-01 13:13:05 +00:00
Jones f42cade9cc 1.21.7 Support 2025-06-30 17:16:16 +02:00
Jones 009dd08cb4 Add missing server versions for 1.21.5-1.21.6 2025-06-30 17:15:53 +02:00
dependabot[bot] 2136da30fa Bump com.gradleup.shadow from 8.3.6 to 8.3.7 (#487)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.6 to 8.3.7.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.6...8.3.7)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-version: 8.3.7
  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>
2025-06-30 11:51:27 +00:00
Jones c63b19f42b Release 2.1.33 (#485) 2025-06-20 13:37:58 +00:00
Akkariin 9943b611ad 1.21.6 Support (#484) 2025-06-19 11:52:41 +00:00
dependabot[bot] f151230f44 Bump com.github.ben-manes.caffeine:caffeine from 3.2.0 to 3.2.1 (#483)
Bumps [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: com.github.ben-manes.caffeine:caffeine
  dependency-version: 3.2.1
  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>
2025-06-16 13:17:12 +00:00
Jones 797b6a6d27 Allow action bar to show CPU usage 2025-06-15 15:09:15 +02:00
Jones dc4b9f5be0 Don't allocate DEFAULT removal listener 2025-06-12 15:38:17 +02:00
Jones 2c9bc89b7d Fix possible exception with short brands 2025-06-11 12:54:42 +02:00
Jones 6c1a56d59f Fix client brand check debug 2025-06-11 12:54:16 +02:00
Jones aa986e2c01 Allow disabling of the duplicate brand check 2025-06-11 12:52:51 +02:00
Jones 3bcd5e5d34 Simplify client brand handling 2025-06-11 12:51:52 +02:00
dependabot[bot] 341e647526 Bump io.netty:netty-all from 4.2.1.Final to 4.2.2.Final (#482)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.1.Final to 4.2.2.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.2.Final
  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>
2025-06-09 00:51:06 +00:00
Jones d7f48ecdf0 Release 2.1.32 (#479) 2025-06-03 15:33:28 +00:00
dependabot[bot] 2c82047ac3 Bump com.diffplug.spotless from 7.0.3 to 7.0.4 (#478)
Bumps com.diffplug.spotless from 7.0.3 to 7.0.4.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 7.0.4
  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>
2025-06-02 12:28:05 +00:00
Jones d03b8ff195 Bump Gradle from 8.14 to 8.14.1 2025-06-01 12:57:26 +02:00
Jones 183bbc0019 Remove unnecessary client brand check 2025-05-31 16:49:39 +02:00
Jones 31282d81c4 Fix false positives with bad cosmetics mods 2025-05-26 15:27:10 +02:00
Jones f0c143fadf Skip Geyser connections by default 2025-05-18 19:38:34 +02:00
Jones 6be001ede8 Release 2.1.31 (#475) 2025-05-16 18:10:07 +00:00
dependabot[bot] 8044b92fb8 Bump io.netty:netty-all from 4.1.119.Final to 4.2.1.Final (#474)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.119.Final to 4.2.1.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.119.Final...netty-4.2.1.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.1.Final
  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>
2025-05-13 14:30:37 +00:00
dependabot[bot] 81b83b5123 Bump adventure-platform from 4.3.4 to 4.4.0 (#473)
Bumps `adventure-platform` from 4.3.4 to 4.4.0.

Updates `net.kyori:adventure-platform-bukkit` from 4.3.4 to 4.4.0
- [Release notes](https://github.com/KyoriPowered/adventure-platform/releases)
- [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.4...v4.4.0)

Updates `net.kyori:adventure-platform-bungeecord` from 4.3.4 to 4.4.0
- [Release notes](https://github.com/KyoriPowered/adventure-platform/releases)
- [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.4...v4.4.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-platform-bukkit
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-platform-bungeecord
  dependency-version: 4.4.0
  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>
2025-05-12 12:25:27 +00:00
Jones a0c054e0a4 Release 2.1.30 (#472) 2025-05-11 10:45:14 +00:00
Jones a668073d17 Bump adventure from 4.20.0 to 4.21.0 2025-05-11 12:33:22 +02:00
Jones 2653754590 Fix Sodium world loading taking too long (#471)
* [wip] Fix Sodium world loading timeout

* Send more chunks because Sodium likes that

* Remove debug-related stuff

* Fix 0,0 chunk not being sent

* Send all chunks to 1.20.3+

* 9 chunks because Sodium is greedy
2025-05-11 10:32:11 +00:00
Jones 7a1d22d922 [ci skip] Update sponsors 2025-05-10 12:16:00 +02:00
Jones f04703405d Fix potential memory leaks on Bukkit (#463)
* Release ByteBuf on invalid handshake data

* Always process non-ByteBuf messages
2025-04-30 15:02:36 +10:00
Jones add066ea0e [ci skip] Update sponsors 2025-04-18 14:30:27 +02:00
Jones 6bc2b1cb82 Release 2.1.29 (#469) 2025-04-17 13:40:14 +02:00
Jones 41e43bcba6 Improve modern CAPTCHA gradient generation 2025-04-17 13:36:49 +02:00
dependabot[bot] d7f150bf6d Bump adventure from 4.18.0 to 4.20.0 (#464)
Bumps `adventure` from 4.18.0 to 4.20.0.

Updates `net.kyori:adventure-text-minimessage` from 4.18.0 to 4.20.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.18.0...v4.20.0)

Updates `net.kyori:adventure-text-serializer-gson` from 4.18.0 to 4.20.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.18.0...v4.20.0)

Updates `net.kyori:adventure-nbt` from 4.18.0 to 4.20.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.18.0...v4.20.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-minimessage
  dependency-version: 4.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-text-serializer-gson
  dependency-version: 4.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-nbt
  dependency-version: 4.20.0
  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>
2025-04-15 14:40:38 +02:00
Jones 37101c665d Hotfix some kind of handler de-sync (#467) 2025-04-15 14:39:18 +02:00
dependabot[bot] 4b3e88fcda Bump com.diffplug.spotless from 7.0.2 to 7.0.3 (#468)
Bumps com.diffplug.spotless from 7.0.2 to 7.0.3.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 7.0.3
  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>
2025-04-14 07:53:19 +02:00
Jones b10a04294d Allow extension of the action-bar handler 2025-04-13 01:18:09 +02:00
Jones 0b06b59f3a Fix CAPTCHA generator testing 2025-04-13 01:16:20 +02:00
dependabot[bot] a09beb9309 Bump org.projectlombok:lombok from 1.18.36 to 1.18.38 (#465)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.36 to 1.18.38.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.36...v1.18.38)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.38
  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>
2025-04-07 17:12:56 +02:00
Jones d67b61fa35 Release 2.1.28 (#461) 2025-03-30 11:29:05 +02:00
JonesandAkkariin 57490a7740 1.21.5 Support (#457)
* Add 1.21.5-pre1 protocol version ID

* Add 1.21.5-pre2 protocol version ID

* Start implementing new packet IDs

* Bump setup-gradle from v3 to v4

* Support 1.21.5-rc1 protocol version ID

* [wip] Implement 1.21.5 registry

* Something must have gone wrong while merging

* Support 1.21.5 protocol version ID

* 1.21.5 Support (#459)

* Update registry codec for 1.21.5

* Update mapping id for 1.21.5

* Fix chunk encoding + why this is needed.

* Fix `SystemChatPacket` decoding on 1.21.5

* Clean up entries in 1.21.5 codec

---------

Co-authored-by: Akkariin <fallencrystal@outlook.com>
2025-03-30 11:22:14 +02:00
Jones 834fd2e609 Increase payload packet size limit 2025-03-29 13:08:15 +01:00
Jones 3c3eeb742c Revert "Don't send SetDefaultSpawnPositionPacket"
This reverts commit 445db48925.
2025-03-24 16:53:20 +01:00
Jones 445db48925 Don't send SetDefaultSpawnPositionPacket 2025-03-24 14:11:55 +01:00
Jones f2667390cc Remove unused clientbound VehicleMovePacket 2025-03-24 14:10:44 +01:00
Jones 2e849bf952 Use single instance for FinishConfigurationPacket 2025-03-24 14:07:24 +01:00
Jones 275e0009a4 Skip Carpet fake players (https://github.com/retrooper/packetevents/pull/1171) 2025-03-23 12:03:42 +01:00
Jones 16802e8002 Bump Gradle from 8.12.1 to 8.13 2025-03-14 18:45:39 +01:00
Jones c3d85a9c4a Release 2.1.27 (#456) 2025-03-12 09:57:32 -05:00
Jones 39686177a1 Automatically clean outdated database entries 2025-03-12 15:51:09 +01:00
Jones 3204f62be6 Automatically reconnect remote databases 2025-03-12 15:41:51 +01:00
Jones 73ddcc9f03 Add missing translations 2025-03-12 15:36:45 +01:00
Jones 13cbb5ae1d Implement legacy CAPTCHA design (configurable) 2025-03-12 15:30:22 +01:00
dependabot[bot] a953df33d9 Bump io.netty:netty-all from 4.1.118.Final to 4.1.119.Final (#455)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.118.Final to 4.1.119.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.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>
2025-03-03 09:31:51 +01:00
Jones af12ef535c Release 2.1.26 (#453) 2025-02-28 16:16:22 +01:00
Jones 7de08ffa98 Fix wrong condition in BungeeCord injector 2025-02-28 16:13:28 +01:00
dependabot[bot] c56d41cd77 Bump io.netty:netty-all from 4.1.117.Final to 4.1.118.Final (#451)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.117.Final to 4.1.118.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.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>
2025-02-17 07:20:44 +01:00
Jones 4b30729138 Release 2.1.25 (#449) 2025-02-16 15:45:44 +01:00
Jones 1254514ad2 Improve BungeeCord injection method 2025-02-15 21:23:15 +01:00
Jones b4673d8e9f Prioritize BungeeCord's channel acceptor 2025-02-15 20:47:16 +01:00
Jones 5eaaf88ec8 Tell BungeeCord users about Velocity 2025-02-15 20:15:22 +01:00
Jones 367ce07cb0 Fix incompatibility with latest BungeeCord 2025-02-15 20:13:14 +01:00
Jones 901b49b063 Release 2.1.24 (#447) 2025-02-13 13:58:38 +01:00
Jones a3cdc08f90 Use a real level name for the JoinGame packet 2025-02-07 11:56:54 +01:00
Jones 150a3575b2 Replace SuppressWarnings with noinspection 2025-02-07 11:56:25 +01:00
Jones 6082bf944d Implement safe Sonar API get() method 2025-02-07 11:55:03 +01:00
Jones e6d20e2b94 Add some basic doc comments to Sonar API 2025-02-07 11:52:03 +01:00
Jones 6f3a8432a7 Merge remote-tracking branch 'origin/main' 2025-02-07 11:49:49 +01:00
Jones 350ae02ff0 Mark ambiguous dimensions as deprecated 2025-02-07 11:47:26 +01:00
dependabot[bot] 32c37c0337 Bump com.gradleup.shadow from 8.3.5 to 8.3.6 (#445)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.5 to 8.3.6.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.5...8.3.6)

---
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>
2025-02-03 01:15:30 +01:00
Jones b8e552dff9 Bump Gradle from 8.12 to 8.12.1 2025-02-01 22:16:36 +01:00
Jones 39902c2c9b Release 2.1.23 (#442) 2025-01-29 16:24:29 +01:00
Jones 3324ee6238 Link documentation in transfer mode section 2025-01-27 15:57:04 +01:00
dependabot[bot] 732a291417 Bump org.jetbrains:annotations from 26.0.1 to 26.0.2 (#441)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 26.0.1 to 26.0.2.
- [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.1...26.0.2)

---
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>
2025-01-27 02:21:12 +01:00
Jones 778ac20e73 Link documentation in language.properties 2025-01-26 16:12:20 +01:00
Renan GabrielandJones 8cc49884ec Add Brazilian Portuguese (pt-BR) translations (#440)
* Add Brazilian Portuguese (pt-BR) translations

- Created translation files for Brazilian Portuguese:
  - `webhook/pt-BR.yml`
  - `messages/pt-BR.yml`
  - `config/pt-BR.yml`

This contribution adds support for Brazilian Portuguese, enhancing accessibility for Portuguese-speaking users.

* Add extended support for pt-br (ISO)

* Ignore casing in language codes

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2025-01-26 16:10:24 +01:00
Jones 798ba4bfd1 Fix race condition in VerifiedPlayerController 2025-01-25 17:52:23 +01:00
Jones 5139a63ff7 Bump caffeine from 3.1.8 to 3.2.0 2025-01-20 14:59:36 +01:00
dependabot[bot] 06d81f1ec0 Bump com.github.ben-manes.caffeine:caffeine from 3.1.8 to 3.2.0 (#436)
Bumps [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) from 3.1.8 to 3.2.0.
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.1.8...v3.2.0)

---
updated-dependencies:
- dependency-name: com.github.ben-manes.caffeine:caffeine
  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>
2025-01-20 14:29:08 +01:00
dependabot[bot] d08de99e03 Bump com.diffplug.spotless from 7.0.1 to 7.0.2 (#435)
Bumps com.diffplug.spotless from 7.0.1 to 7.0.2.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  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>
2025-01-20 14:22:34 +01:00
dependabot[bot] 411cadb5c3 Bump io.netty:netty-all from 4.1.116.Final to 4.1.117.Final (#437)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.116.Final to 4.1.117.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.116.Final...netty-4.1.117.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>
2025-01-20 14:22:11 +01:00
87 changed files with 1790 additions and 542 deletions

No files matched your search

+4
View File
@@ -53,6 +53,10 @@ 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>
### Past Sponsors
<a href="https://github.com/ItzErpandX"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/84748484?v=4?s=400?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>
+4 -3
View File
@@ -29,10 +29,11 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
uses: gradle/gradle-build-action@v3
with:
arguments: build-sonar
run: ./gradlew build-sonar
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v4
@@ -49,22 +49,68 @@ public interface Sonar {
@ApiStatus.Internal
@NotNull Audience sender(final @NotNull Object object);
/**
* Gets the platform Sonar is running on (e.g., Bukkit, BungeeCord, Velocity).
*
* @return The {@link SonarPlatform} enum representing the current platform.
*/
@NotNull SonarPlatform getPlatform();
/**
* Gets the logger instance for Sonar.
*
* @return The {@link LoggerWrapper} instance.
*/
@NotNull LoggerWrapper getLogger();
/**
* Gets the Sonar configuration.
*
* @return The {@link SonarConfiguration} instance.
*/
@NotNull SonarConfiguration getConfig();
/**
* Gets the subcommand registry for Sonar commands.
*
* @return The {@link SubcommandRegistry} instance.
*/
@NotNull SubcommandRegistry getSubcommandRegistry();
/**
* Gets the controller for verified players.
* Used for managing players who have passed the verification.
*
* @return The {@link VerifiedPlayerController} instance.
*/
VerifiedPlayerController getVerifiedPlayerController();
/**
* Gets the handler for action bar notifications.
*
* @return The {@link ActionBarNotificationHandler} instance.
*/
@NotNull ActionBarNotificationHandler getActionBarNotificationHandler();
/**
* Gets the handler for chat notifications.
*
* @return The {@link ChatNotificationHandler} instance.
*/
@NotNull ChatNotificationHandler getChatNotificationHandler();
/**
* Gets the system timer used for tracking plugin launch time.
*
* @return The {@link SystemTimer} instance.
*/
@NotNull SystemTimer getLaunchTimer();
/**
* Gets the statistics tracker for Sonar.
*
* @return The {@link SonarStatistics} instance.
*/
@NotNull SonarStatistics getStatistics();
@SuppressWarnings("unused")
@@ -73,25 +119,67 @@ public interface Sonar {
@SuppressWarnings("unused")
void setChatNotificationHandler(final @NotNull ChatNotificationHandler notificationHandler);
/**
* Reloads the Sonar configuration.
*/
void reload();
/**
* Gets the current version of Sonar.
*
* @return The {@link SonarVersion} instance.
*/
@NotNull default SonarVersion getVersion() {
return SonarVersion.INSTANCE;
}
/**
* Gets the event manager for Sonar.
*
* @return The {@link SonarEventManager} instance.
*/
@NotNull default SonarEventManager getEventManager() {
return SonarEventManager.INSTANCE;
}
/**
* Gets the attack tracker for Sonar.
*
* @return The {@link AttackTracker} instance.
*/
@NotNull default AttackTracker getAttackTracker() {
return AttackTracker.INSTANCE;
}
/**
* Gets the anti-bot handler (called "Fallback") for Sonar.
*
* @return The {@link Fallback} instance.
*/
@NotNull default Fallback getFallback() {
return Fallback.INSTANCE;
}
/**
* Gets the instance of the Sonar API.
*
* @return The {@link Sonar} instance.
* @throws IllegalStateException If Sonar has not been initialized.
*/
@NotNull static Sonar get() {
return SonarSupplier.get();
}
/**
* Gets the instance of the Sonar API.
* Only use this method if you understand the implications,
* and have a specific reason to bypass the standard {@link #get()} method.
* In most cases, {@link #get()} is the preferred way to access the Sonar API.
*
* @return The Sonar instance.
*/
@ApiStatus.Internal
@NotNull static Sonar get0() {
return SonarSupplier.sonar;
}
}
@@ -30,4 +30,11 @@ public class SonarSupplier {
}
sonar = _sonar;
}
public Sonar get() {
if (sonar == null) {
throw new IllegalStateException("Sonar is not initialized yet");
}
return sonar;
}
}
@@ -36,8 +36,8 @@ public interface SonarCommand {
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")) {
if (Sonar.get().getConfig().getNoPermission() != null) {
source.sendMessage(Sonar.get().getConfig().getNoPermission());
if (Sonar.get0().getConfig().getNoPermission() != null) {
source.sendMessage(Sonar.get0().getConfig().getNoPermission());
}
return;
}
@@ -46,7 +46,7 @@ public interface SonarCommand {
if (args.length > 0) {
// Search subcommand if command arguments are present
subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().stream()
subcommand = Sonar.get0().getSubcommandRegistry().getSubcommands().stream()
.filter(sub -> sub.getInfo().name().equalsIgnoreCase(args[0])
|| Arrays.stream(sub.getInfo().aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0])))
.findFirst();
@@ -57,8 +57,8 @@ public interface SonarCommand {
if (!command.getInfo().onlyConsole()
&& !source.getPermissionFunction().test(command.getPermission())) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.subcommand-no-permission"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.subcommand-no-permission"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("permission", command.getPermission())));
return;
}
@@ -66,28 +66,28 @@ public interface SonarCommand {
command.invoke(source, args);
}, () -> {
final UpdateChecker.CheckResult checkResult = UpdateChecker.getLastCheckResult();
Component version = Component.text(Sonar.get().getVersion().getFormatted());
Component version = Component.text(Sonar.get0().getVersion().getFormatted());
if (checkResult.getConfigKey() != null) {
version = version.append(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.main." + checkResult.getConfigKey())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.main." + checkResult.getConfigKey())));
}
// Print standard help message
source.sendMessage(MiniMessage.miniMessage().deserialize(
String.join("<newline>",
Sonar.get().getConfig().getMessagesConfig().getStringList("commands.main.header")),
Sonar.get0().getConfig().getMessagesConfig().getStringList("commands.main.header")),
Placeholder.component("version", version),
Placeholder.unparsed("platform", Sonar.get().getPlatform().getDisplayName()),
Placeholder.unparsed("platform", Sonar.get0().getPlatform().getDisplayName()),
Placeholder.unparsed("copyright-year", String.valueOf(Calendar.getInstance().get(Calendar.YEAR)))));
final Component yes = MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.tick"));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.main.tick"));
final Component no = MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.cross"));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.main.cross"));
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(command -> {
Sonar.get0().getSubcommandRegistry().getSubcommands().forEach(command -> {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.subcommands"),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.main.subcommands"),
TagResolver.resolver("suggest-subcommand",
(queue, context) -> Tag.styling(ClickEvent.suggestCommand("/sonar " + command.getInfo().name()))),
Placeholder.unparsed("subcommand", command.getInfo().name()),
@@ -103,7 +103,7 @@ public interface SonarCommand {
static void prepareCachedTabSuggestions() {
if (TAB_SUGGESTIONS.isEmpty()) {
final List<String> subcommandsAndAliases = new ArrayList<>();
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
for (final Subcommand subcommand : Sonar.get0().getSubcommandRegistry().getSubcommands()) {
subcommandsAndAliases.add(subcommand.getInfo().name());
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
@@ -48,8 +48,8 @@ public abstract class Subcommand {
protected static @Nullable String validateIP(final InvocationSource source, final String raw) {
if (!IPv4_REGEX.matcher(raw).matches() && !IPv6_REGEX.matcher(raw).matches()) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.invalid-ip-address"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.invalid-ip-address"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return null;
}
return raw;
@@ -61,8 +61,8 @@ public abstract class Subcommand {
protected final void incorrectUsage(final @NotNull InvocationSource invocationSource, final String usage) {
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.incorrect-usage"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.incorrect-usage"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("subcommand-usage", usage)));
}
@@ -70,16 +70,16 @@ public abstract class Subcommand {
// Check if the subcommand can only be executed by players
if (info.onlyPlayers() && !invocationSource.isPlayer()) {
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.player-only"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.player-only"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
// Check if the subcommand can only be executed though console
if (info.onlyConsole() && invocationSource.isPlayer()) {
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.console-only"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.console-only"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
@@ -95,7 +95,7 @@ public abstract class Subcommand {
public final @NotNull String getDescription() {
final String path = String.format("commands.%s.description", info.name());
return Objects.requireNonNull(Sonar.get().getConfig().getMessagesConfig().getString(path));
return Objects.requireNonNull(Sonar.get0().getConfig().getMessagesConfig().getString(path));
}
protected abstract void execute(final @NotNull InvocationSource source, final String @NotNull [] args);
@@ -140,6 +140,7 @@ public enum Language {
FA("Persian"),
PL("Polish"),
PT("Portuguese"),
PT_BR("Brazilian Portuguese", "pt-br"),
PS("Pushto"),
QU("Quechua"),
RM("Raeto-Romance"),
@@ -203,7 +204,18 @@ public enum Language {
this.code = name().toLowerCase();
}
Language(final String name, final String code) {
this.name = name;
this.code = code;
}
public static Language fromCode(final @NotNull String code) {
return Language.valueOf(code.toUpperCase());
for (final Language language : values()) {
if (language.code.equalsIgnoreCase(code)) {
return language;
}
}
// Fallback to English
return EN;
}
}
@@ -89,7 +89,7 @@ public final class SimpleYamlConfig {
public int getInt(final String path) {
if (!yaml.contains(path)) {
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
Sonar.get0().getLogger().warn("Could not find {} in {}.", path, file.getName());
return 0;
}
return yaml.getInt(path);
@@ -97,7 +97,7 @@ public final class SimpleYamlConfig {
public boolean getBoolean(final String path) {
if (!yaml.contains(path)) {
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
Sonar.get0().getLogger().warn("Could not find {} in {}.", path, file.getName());
return false;
}
return yaml.getBoolean(path);
@@ -105,7 +105,7 @@ public final class SimpleYamlConfig {
public @NotNull String getString(final String path) {
if (!yaml.contains(path)) {
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
Sonar.get0().getLogger().warn("Could not find {} in {}.", path, file.getName());
return "";
}
final Object object = yaml.get(path);
@@ -117,7 +117,7 @@ public final class SimpleYamlConfig {
public List<String> getStringList(final String path) {
if (!yaml.contains(path)) {
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
Sonar.get0().getLogger().warn("Could not find {} in {}.", path, file.getName());
return new ArrayList<>(0);
}
return yaml.getStringList(path);
@@ -125,7 +125,7 @@ public final class SimpleYamlConfig {
public List<Integer> getIntList(final String path) {
if (!yaml.contains(path)) {
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
Sonar.get0().getLogger().warn("Could not find {} in {}.", path, file.getName());
return new ArrayList<>(0);
}
return yaml.getIntegerList(path);
@@ -66,14 +66,14 @@ public final class SonarConfiguration {
final URL defaultLanguageFile = Sonar.class.getResource("/assets/language.properties");
// Make sure the file actually exists before trying to copy it
if (defaultLanguageFile == null) {
Sonar.get().getLogger().error("Cannot check for custom language (is the file missing?)");
Sonar.get0().getLogger().error("Cannot check for custom language (is the file missing?)");
return DEFAULT_FALLBACK_LANGUAGE;
}
// Copy the file to the plugin data directory
try (final InputStream inputStream = defaultLanguageFile.openStream()) {
Files.copy(inputStream, languageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException exception) {
Sonar.get().getLogger().error("Error copying file: {}", exception);
Sonar.get0().getLogger().error("Error copying file: {}", exception);
return DEFAULT_FALLBACK_LANGUAGE;
}
}
@@ -87,13 +87,13 @@ public final class SonarConfiguration {
// Try parsing the property as a language
return Language.fromCode(property);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not find requested language: {}", throwable);
Sonar.get().getLogger().error("You can view a full list of valid language codes here:");
Sonar.get().getLogger().error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
Sonar.get().getLogger().error("If a translation does not exist yet, Sonar will use English (en).");
Sonar.get0().getLogger().error("Could not find requested language: {}", throwable);
Sonar.get0().getLogger().error("You can view a full list of valid language codes here:");
Sonar.get0().getLogger().error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
Sonar.get0().getLogger().error("If a translation does not exist yet, Sonar will use English (en).");
}
} catch (IOException exception) {
Sonar.get().getLogger().error("Error reading language file: {}", exception);
Sonar.get0().getLogger().error("Error reading language file: {}", exception);
}
return DEFAULT_FALLBACK_LANGUAGE;
}
@@ -112,13 +112,13 @@ public final class SonarConfiguration {
final String property = System.getProperty("user.language", "en");
language = Language.fromCode(property);
// Make sure the user knows that we're using the system language for translations
Sonar.get().getLogger().info("Using system language ({}) for translations.", language);
Sonar.get0().getLogger().info("Using system language ({}) for translations.", language);
} catch (Exception exception) {
Sonar.get().getLogger().warn("Could not use system language for translations.");
Sonar.get().getLogger().warn("Using default language ({}) for translations.", language);
Sonar.get0().getLogger().warn("Could not use system language for translations.");
Sonar.get0().getLogger().warn("Using default language ({}) for translations.", language);
}
} else {
Sonar.get().getLogger().info("Using custom language ({}) for translations.", language);
Sonar.get0().getLogger().info("Using custom language ({}) for translations.", language);
}
// Load all configurations
@@ -158,12 +158,12 @@ public final class SonarConfiguration {
verification.timing = Verification.Timing.valueOf(generalConfig.getString("verification.timing"));
// Warn the user if the verification timing is set to NEVER
if (verification.timing == Verification.Timing.NEVER) {
Sonar.get().getLogger().warn(" ");
Sonar.get().getLogger().warn("You have set the verification timing to 'NEVER'.");
Sonar.get().getLogger().warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
Sonar.get().getLogger().warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
Sonar.get().getLogger().warn("Please only edit this option if you really know what you are doing.");
Sonar.get().getLogger().warn(" ");
Sonar.get0().getLogger().warn(" ");
Sonar.get0().getLogger().warn("You have set the verification timing to 'NEVER'.");
Sonar.get0().getLogger().warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
Sonar.get0().getLogger().warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
Sonar.get0().getLogger().warn("Please only edit this option if you really know what you are doing.");
Sonar.get0().getLogger().warn(" ");
}
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled");
@@ -187,7 +187,7 @@ public final class SonarConfiguration {
if (backgroundFile.exists()) {
verification.map.backgroundImage = backgroundFile;
} else {
Sonar.get().getLogger().error("Could not find background image {}", backgroundFile.getAbsolutePath());
Sonar.get0().getLogger().error("Could not find background image {}", backgroundFile.getAbsolutePath());
}
}
@@ -288,7 +288,7 @@ public final class SonarConfiguration {
final String resourceName = url + "/" + language.getCode() + ".yml";
URL result = Sonar.class.getResource("/assets/" + resourceName);
if (result == null) {
Sonar.get().getLogger().warn("Could not find " + resourceName + "! Using en.yml!");
Sonar.get0().getLogger().warn("Could not find " + resourceName + "! Using en.yml!");
result = Objects.requireNonNull(Sonar.class.getResource("/assets/" + url + "/en.yml"));
}
return result;
@@ -297,7 +297,7 @@ public final class SonarConfiguration {
private static int clamp(final int v, final int max, final int min) {
final int output = Math.max(Math.min(v, min), max);
if (output != v) {
Sonar.get().getLogger().warn("Clamped configuration value {} to {}", v, output);
Sonar.get0().getLogger().warn("Clamped configuration value {} to {}", v, output);
}
return output;
}
@@ -447,14 +447,14 @@ public final class SonarConfiguration {
@Getter
@RequiredArgsConstructor
public enum Type {
MYSQL("MySQL", "jdbc:mysql://%s:%d/%s", new MysqlDatabaseTypeAdapter(),
MYSQL("MySQL", "jdbc:mysql://%s:%d/%s%s", new MysqlDatabaseTypeAdapter(),
Library.builder()
.groupId("com{}mysql")
.artifactId("mysql-connector-j")
.version("9.0.0")
.relocate("com{}mysql", "xyz{}jonesdev{}sonar{}libs{}mysql")
.build()),
MARIADB("MariaDB", "jdbc:mariadb://%s:%d/%s", new MariaDbDatabaseTypeAdapter(),
MARIADB("MariaDB", "jdbc:mariadb://%s:%d/%s%s", new MariaDbDatabaseTypeAdapter(),
Library.builder()
.groupId("org{}mariadb{}jdbc")
.artifactId("mariadb-java-client")
@@ -54,19 +54,21 @@ public final class VerifiedPlayerController {
@Getter
private final @NotNull SonarConfiguration.Database.Type cachedDatabaseType;
private final ExecutorService updateService = Executors.newSingleThreadExecutor();
@Getter
private int maximumAge;
public VerifiedPlayerController(final @NotNull LibraryManager libraryManager) {
final SonarConfiguration.Database database = Sonar.get().getConfig().getDatabase();
final SonarConfiguration.Database database = Sonar.get0().getConfig().getDatabase();
cachedDatabaseType = database.getType();
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
Sonar.get().getLogger().warn("Configure a database to save verified players.");
Sonar.get0().getLogger().warn("Configure a database to save verified players.");
return;
}
// Make sure to only load the driver once per database type
if (!cachedDatabaseType.isLoaded()) {
Sonar.get().getLogger().info("Loading {} driver version {}",
Sonar.get0().getLogger().info("Loading {} driver version {}",
cachedDatabaseType.getDatabaseType().getDatabaseName(),
cachedDatabaseType.getDatabaseDriver().getVersion());
libraryManager.loadLibrary(cachedDatabaseType.getDatabaseDriver());
@@ -79,20 +81,21 @@ public final class VerifiedPlayerController {
// H2 has a different JDBC URL layout
// https://www.codejava.net/java-se/jdbc/connect-to-h2-database-examples
if (cachedDatabaseType == SonarConfiguration.Database.Type.H2) {
final File file = new File(Sonar.get().getConfig().getPluginFolder(),
Sonar.get().getConfig().getGeneralConfig().getString("database.filename"));
final File file = new File(Sonar.get0().getConfig().getPluginFolder(),
Sonar.get0().getConfig().getGeneralConfig().getString("database.filename"));
jdbcURL = String.format(cachedDatabaseType.getConnectionString(), file.getAbsolutePath());
} else {
// Normal JDBC URL layout for MySQL/MariaDB/...
jdbcURL = String.format(cachedDatabaseType.getConnectionString(),
Sonar.get().getConfig().getGeneralConfig().getString("database.host"),
Sonar.get().getConfig().getGeneralConfig().getInt("database.port"),
Sonar.get().getConfig().getGeneralConfig().getString("database.name"));
Sonar.get0().getConfig().getGeneralConfig().getString("database.host"),
Sonar.get0().getConfig().getGeneralConfig().getInt("database.port"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.name"),
"?autoReconnect=true");
}
connectionSource = new JdbcConnectionSource(jdbcURL,
Sonar.get().getConfig().getGeneralConfig().getString("database.username"),
Sonar.get().getConfig().getGeneralConfig().getString("database.password"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.username"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.password"),
cachedDatabaseType.getDatabaseType());
try {
@@ -107,6 +110,7 @@ public final class VerifiedPlayerController {
dao = DaoManager.createDao(connectionSource, VerifiedPlayer.class);
queryBuilder = dao.queryBuilder();
maximumAge = database.getMaximumAge();
// Make sure to run the clean task and the caching task in the same thread
// https://github.com/jonesdevelopment/sonar/issues/150
@@ -114,7 +118,7 @@ public final class VerifiedPlayerController {
if (connectionSource != null) {
try {
// Make sure to clear all outdated entries first
clearOld(database.getMaximumAge());
clearOld(maximumAge, false);
// Add all entries from the database to the cache
dao.queryForAll().forEach(verifiedPlayer -> cache.add(verifiedPlayer.getFingerprint()));
} catch (SQLException exception) {
@@ -133,6 +137,8 @@ public final class VerifiedPlayerController {
public void close() {
// The connection source will always be null if the database type is NONE.
if (connectionSource != null) {
// Shut down the update service before letting it re-use the connection.
updateService.shutdown();
try {
connectionSource.close();
} catch (Exception exception) {
@@ -140,14 +146,14 @@ public final class VerifiedPlayerController {
}
// Make sure not to reuse a closed connection
connectionSource = null;
updateService.shutdown();
}
}
/**
* Clear all old entries using the given timestamp
*/
private void clearOld(final @Range(from = 1, to = 365) int maximumAge) throws SQLException {
public void clearOld(final @Range(from = 1, to = 365) int maximumAge,
final boolean removeExisting) throws SQLException {
final long timestamp = Instant.now().minus(maximumAge, ChronoUnit.DAYS).getEpochSecond() * 1000L;
final List<VerifiedPlayer> oldEntries = queryBuilder.where()
@@ -157,8 +163,11 @@ public final class VerifiedPlayerController {
if (oldEntries != null && !oldEntries.isEmpty()) {
for (final VerifiedPlayer player : oldEntries) {
dao.delete(player);
if (removeExisting) {
cache.remove(player.getFingerprint());
}
}
Sonar.get().getLogger().info("Removed {} database entries older than {} days.",
Sonar.get0().getLogger().info("Removed {} database entries older than {} days.",
oldEntries.size(), maximumAge);
}
}
@@ -47,7 +47,7 @@ public final class SonarEventManager {
try {
eventListener.handle(event);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not pass {} to listener: {}",
Sonar.get0().getLogger().error("Could not pass {} to listener: {}",
event.getClass().getSimpleName(), throwable);
}
}
@@ -49,16 +49,16 @@ public final class Fallback {
private Ratelimiter<InetAddress> ratelimiter;
public boolean shouldVerifyNewPlayers() {
return shouldPerform(Sonar.get().getConfig().getVerification().getTiming());
return shouldPerform(Sonar.get0().getConfig().getVerification().getTiming());
}
public boolean shouldPerformCaptcha() {
return shouldPerform(Sonar.get().getConfig().getVerification().getMap().getTiming());
return shouldPerform(Sonar.get0().getConfig().getVerification().getMap().getTiming());
}
private static boolean shouldPerform(final SonarConfiguration.Verification.Timing timing) {
return timing == SonarConfiguration.Verification.Timing.ALWAYS
|| (timing == SonarConfiguration.Verification.Timing.DURING_ATTACK
&& Sonar.get().getAttackTracker().getCurrentAttack() != null);
&& Sonar.get0().getAttackTracker().getCurrentAttack() != null);
}
}
@@ -38,7 +38,7 @@ public final class FallbackLoginQueue {
private final ConcurrentMap<InetAddress, Runnable> players = new ConcurrentHashMap<>(512);
public void poll() {
final int maxQueuePolls = Math.min(players.size(), Sonar.get().getConfig().getQueue().getMaxQueuePolls());
final int maxQueuePolls = Math.min(players.size(), Sonar.get0().getConfig().getQueue().getMaxQueuePolls());
// No need to initiate an executor service task if nobody is currently queued
if (maxQueuePolls <= 0) return;
// We need to be cautious here since we don't want any concurrency issues
@@ -70,7 +70,10 @@ public enum ProtocolVersion {
MINECRAFT_1_20_5(766, "1.20.5"),
MINECRAFT_1_21(767, "1.21"),
MINECRAFT_1_21_2(768, "1.21.2"),
MINECRAFT_1_21_4(769, "1.21.4");
MINECRAFT_1_21_4(769, "1.21.4"),
MINECRAFT_1_21_5(770, "1.21.5"),
MINECRAFT_1_21_6(771, "1.21.6"),
MINECRAFT_1_21_7(772, "1.21.7");
private final int protocol;
private final String name;
@@ -33,8 +33,8 @@ import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
import static xyz.jonesdev.sonar.api.timer.SystemTimer.DATE_FORMATTER;
@Getter
public final class ActionBarNotificationHandler extends NotificationHandler {
private int animationIndex;
public class ActionBarNotificationHandler extends NotificationHandler {
protected int animationIndex;
@Override
public void handleNotification() {
@@ -44,29 +44,31 @@ public final class ActionBarNotificationHandler extends NotificationHandler {
}
// Prepare the action bar verbose
final AttackTracker.AttackStatistics attackStatistics = Sonar.get().getAttackTracker().getCurrentAttack();
final AttackTracker.AttackStatistics attackStatistics = Sonar.get0().getAttackTracker().getCurrentAttack();
final SystemTimer attackTimer = attackStatistics == null ? null : attackStatistics.getDuration();
final String attackDuration = attackTimer == null ? "00:00" : DATE_FORMATTER.format(attackTimer.delay());
final Component actionBarComponent = MiniMessage.miniMessage().deserialize(attackTimer == null
? Sonar.get().getConfig().getMessagesConfig().getString("verbose.layout.normal")
: Sonar.get().getConfig().getMessagesConfig().getString("verbose.layout.attack"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
? Sonar.get0().getConfig().getMessagesConfig().getString("verbose.layout.normal")
: Sonar.get0().getConfig().getMessagesConfig().getString("verbose.layout.attack"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("attack-duration", attackDuration),
Placeholder.unparsed("animation", nextAnimation()),
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("process-cpu", Sonar.DECIMAL_FORMAT.format(getProcessCPUUsage())),
Placeholder.unparsed("system-cpu", Sonar.DECIMAL_FORMAT.format(getSystemCPUUsage())),
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get0().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get0().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -74,7 +76,7 @@ public final class ActionBarNotificationHandler extends NotificationHandler {
// Send the action bar to all online players
for (final UUID subscriber : subscribers) {
final Audience audience = Sonar.get().audience(subscriber);
final Audience audience = Sonar.get0().audience(subscriber);
if (audience == null) continue;
// Send the action bar to all online subscribers
@@ -83,7 +85,7 @@ public final class ActionBarNotificationHandler extends NotificationHandler {
}
public String nextAnimation() {
final var animations = Sonar.get().getConfig().getVerboseAnimation();
final var animations = Sonar.get0().getConfig().getVerboseAnimation();
final int nextIndex = ++animationIndex % animations.size();
return animations.toArray(new String[0])[nextIndex];
}
@@ -43,30 +43,30 @@ public final class ChatNotificationHandler extends NotificationHandler {
// Prepare the chat notification
final Component notificationTitle = MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("notifications.title"));
Sonar.get0().getConfig().getMessagesConfig().getString("notifications.title"));
final Component notificationSubtitle = MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("notifications.subtitle"));
Sonar.get0().getConfig().getMessagesConfig().getString("notifications.subtitle"));
final Title title = Title.title(notificationTitle, notificationSubtitle);
final List<String> chatNotification = Sonar.get().getConfig().getMessagesConfig().getStringList("notifications.chat");
final List<String> chatNotification = Sonar.get0().getConfig().getMessagesConfig().getStringList("notifications.chat");
final Component[] chatNotificationComponents = new Component[chatNotification.size()];
for (int i = 0; i < chatNotificationComponents.length; i++) {
chatNotificationComponents[i] = MiniMessage.miniMessage().deserialize(chatNotification.get(i),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get0().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get0().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -74,8 +74,8 @@ public final class ChatNotificationHandler extends NotificationHandler {
}
// Send the title and chat messages to all online players
for (final UUID subscriber : Sonar.get().getChatNotificationHandler().getSubscribers()) {
final Audience audience = Sonar.get().audience(subscriber);
for (final UUID subscriber : Sonar.get0().getChatNotificationHandler().getSubscribers()) {
final Audience audience = Sonar.get0().audience(subscriber);
if (audience == null) continue;
// Send the message to all online subscribers
@@ -52,10 +52,10 @@ public final class AttackTracker {
}
public void checkIfUnderAttack() {
final long joinsPerSecond = Sonar.get().getStatistics().getLoginsPerSecond();
final int verifyingPlayers = Sonar.get().getFallback().getConnected().size();
final int queuedPlayers = Sonar.get().getFallback().getQueue().getPlayers().size();
final int minPlayers = Sonar.get().getConfig().getMinPlayersForAttack();
final long joinsPerSecond = Sonar.get0().getStatistics().getLoginsPerSecond();
final int verifyingPlayers = Sonar.get0().getFallback().getConnected().size();
final int queuedPlayers = Sonar.get0().getFallback().getQueue().getPlayers().size();
final int minPlayers = Sonar.get0().getConfig().getMinPlayersForAttack();
if (joinsPerSecond > minPlayers // Check the number of bots/joins per second.
|| verifyingPlayers > minPlayers // Check the number of verifying players.
@@ -66,10 +66,10 @@ public final class AttackTracker {
// An attack has been detected
if (currentAttack == null) {
currentAttack = new AttackStatistics();
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().getCache().size();
currentAttack.failedVerifications = Sonar.get().getStatistics().getTotalFailedVerifications();
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
Sonar.get().getChatNotificationHandler().handleNotification();
currentAttack.successfulVerifications = Sonar.get0().getVerifiedPlayerController().getCache().size();
currentAttack.failedVerifications = Sonar.get0().getStatistics().getTotalFailedVerifications();
Sonar.get0().getEventManager().publish(new AttackDetectedEvent());
Sonar.get0().getChatNotificationHandler().handleNotification();
} else {
// Reset attack timer if we're still under attack
currentAttack.timer.reset();
@@ -80,7 +80,7 @@ public final class AttackTracker {
// Update joins per second peak if necessary
currentAttack.peakJoinsPerSecond = joinsPerSecond;
}
final long connectionsPerSecond = Sonar.get().getStatistics().getConnectionsPerSecond();
final long connectionsPerSecond = Sonar.get0().getStatistics().getConnectionsPerSecond();
if (connectionsPerSecond > currentAttack.peakConnectionsPerSecond) {
// Update connections per second peak if necessary
currentAttack.peakConnectionsPerSecond = connectionsPerSecond;
@@ -96,14 +96,14 @@ public final class AttackTracker {
currentAttack.peakProcessMemoryUsage = processMemoryUsage;
}
} else if (currentAttack != null) {
if (currentAttack.duration.delay() > Sonar.get().getConfig().getMinAttackDuration()
&& currentAttack.timer.delay() > Sonar.get().getConfig().getAttackCooldownDelay()) {
if (currentAttack.duration.delay() > Sonar.get0().getConfig().getMinAttackDuration()
&& currentAttack.timer.delay() > Sonar.get0().getConfig().getAttackCooldownDelay()) {
// The current attack has stopped
Sonar.get().getEventManager().publish(new AttackMitigatedEvent(currentAttack));
Sonar.get0().getEventManager().publish(new AttackMitigatedEvent(currentAttack));
if (++attackThreshold > Sonar.get().getConfig().getMinAttackThreshold()) {
if (++attackThreshold > Sonar.get0().getConfig().getMinAttackThreshold()) {
// Post webhook to Discord
Optional.ofNullable(Sonar.get().getConfig().getWebhook().getDiscordWebhook())
Optional.ofNullable(Sonar.get0().getConfig().getWebhook().getDiscordWebhook())
.ifPresent(webhook -> {
// Save attack statistics
final long deltaInMillis = currentAttack.duration.delay();
@@ -116,15 +116,15 @@ public final class AttackTracker {
final String formattedDuration = String.format("%d minutes, %.0f seconds", minutes, seconds);
final String startTimestamp = String.valueOf(currentAttack.duration.getStart() / 1000L);
final String endTimestamp = String.valueOf(System.currentTimeMillis() / 1000L);
final long blacklisted = Sonar.get().getFallback().getBlacklist().estimatedSize();
final long blacklisted = Sonar.get0().getFallback().getBlacklist().estimatedSize();
// Calculate during-attack-statistics using their deltas
final long totalVerified = Sonar.get().getVerifiedPlayerController().getCache().size();
final long totalVerified = Sonar.get0().getVerifiedPlayerController().getCache().size();
final long verified = Math.max(totalVerified - currentAttack.successfulVerifications, 0);
final long totalFailed = Sonar.get().getStatistics().getTotalFailedVerifications();
final long totalFailed = Sonar.get0().getStatistics().getTotalFailedVerifications();
final long failed = Math.max(totalFailed - currentAttack.failedVerifications, 0);
webhook.post(() -> {
final SonarConfiguration.Webhook.Embed config = Sonar.get().getConfig().getWebhook().getEmbed();
final SonarConfiguration.Webhook.Embed config = Sonar.get0().getConfig().getWebhook().getEmbed();
final String description = config.getDescription()
.replace("<start-timestamp>", startTimestamp)
.replace("<end-timestamp>", endTimestamp)
@@ -48,23 +48,23 @@ public class UpdateChecker {
final JsonObject json = parseJson(urlConnection);
final String latestStableRelease = json.get("tag_name").getAsString();
final int convertedLatestVersion = convertVersion(latestStableRelease);
final int convertedCurrentVersion = convertVersion(Sonar.get().getVersion().getVersion());
final int convertedCurrentVersion = convertVersion(Sonar.get0().getVersion().getVersion());
if (convertedCurrentVersion < convertedLatestVersion) {
Sonar.get().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/latest");
Sonar.get0().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get0().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get0().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/latest");
lastCheckResult = CheckResult.OUTDATED_VERSION;
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().getGitBranch().equals("main")) {
Sonar.get().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get0().getVersion().getGitBranch().equals("main")) {
Sonar.get0().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get0().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
lastCheckResult = CheckResult.UNSTABLE_VERSION;
} else if (lastCheckResult != CheckResult.LATEST_VERSION) {
Sonar.get().getLogger().info("You are currently using the latest stable release of Sonar!");
Sonar.get0().getLogger().info("You are currently using the latest stable release of Sonar!");
lastCheckResult = CheckResult.LATEST_VERSION;
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Unable to retrieve version information: {}", throwable);
Sonar.get0().getLogger().warn("Unable to retrieve version information: {}", throwable);
lastCheckResult = CheckResult.API_ERROR;
}
});
@@ -59,18 +59,18 @@ public final class DiscordWebhook {
// Discord replies with HTTP_NO_CONTENT (204) if the request is successful
final int code = urlConnection.getResponseCode();
if (code != HTTP_NO_CONTENT) {
Sonar.get().getLogger().warn("Unexpected Discord webhook response code {}", code);
Sonar.get0().getLogger().warn("Unexpected Discord webhook response code {}", code);
}
} catch (Exception exception) {
Sonar.get().getLogger().error("Could not send webhook: {}", exception);
Sonar.get0().getLogger().error("Could not send webhook: {}", exception);
}
});
}
private static String prepareSerializedContent(final SonarConfiguration.Webhook.@NotNull Embed embed) {
final String content = Sonar.get().getConfig().getWebhook().getContent();
final String username = Sonar.get().getConfig().getWebhook().getUsername();
final String avatarUrl = Sonar.get().getConfig().getWebhook().getAvatarUrl();
final String content = Sonar.get0().getConfig().getWebhook().getContent();
final String username = Sonar.get0().getConfig().getWebhook().getUsername();
final String avatarUrl = Sonar.get0().getConfig().getWebhook().getAvatarUrl();
int rgb = embed.getR();
rgb = (rgb << 8) + embed.getG();
@@ -78,8 +78,8 @@ public final class DiscordWebhook {
final Webhook.EmbedMessage embedMessage = new Webhook.EmbedMessage(
embed.getTitle(), embed.getDescription(), embed.getTitleUrl(), rgb, new Webhook.Field[0],
new Webhook.Footer(Sonar.get().getConfig().getWebhook().getFooter().getText(),
Sonar.get().getConfig().getWebhook().getFooter().getIconUrl()));
new Webhook.Footer(Sonar.get0().getConfig().getWebhook().getFooter().getText(),
Sonar.get0().getConfig().getWebhook().getFooter().getIconUrl()));
final List<Webhook.EmbedMessage> embeds = Collections.singletonList(embedMessage);
final Webhook webhook = new Webhook(content, username, avatarUrl, false, embeds);
@@ -40,7 +40,7 @@ public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, S
// Create our own invocation source wrapper to handle messages properly
final InvocationSource invocationSource = new InvocationSource(
sender instanceof Player ? ((Player) sender).getUniqueId() : null,
Sonar.get().sender(sender),
Sonar.get0().sender(sender),
sender::hasPermission);
// Pass the invocation source and command arguments to our command handler
handle(invocationSource, args);
@@ -74,7 +74,10 @@ public enum BukkitServerVersion {
MINECRAFT_1_21_1,
MINECRAFT_1_21_2,
MINECRAFT_1_21_3,
MINECRAFT_1_21_4;
MINECRAFT_1_21_4,
MINECRAFT_1_21_5,
MINECRAFT_1_21_6,
MINECRAFT_1_21_7;
static final BukkitServerVersion[] REVERSED_VALUES;
@@ -45,7 +45,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
FallbackBukkitInboundHandler() {
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
channelRemovalListener = (pipeline, name, handler) -> {
super.channelRemovalListener = (pipeline, name, handler) -> {
final ChannelInactiveListener inactiveListener = pipeline.get(ChannelInactiveListener.class);
if (inactiveListener != null) {
@@ -122,6 +122,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
default:
byteBuf.release();
throw ProtocolUtil.DEBUG ? new DecoderException("Bad handshake intent " + handshake.getIntent())
: QuietDecoderException.INSTANCE;
}
@@ -136,7 +137,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
ctx.fireChannelRead(byteBuf.retain());
// TODO: recode this?
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
if (inboundHandler != null) {
if (inboundHandler != null && channelRemovalListener != null) {
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
}
}, loginStart.getUsername(), socketAddress);
@@ -146,6 +147,8 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf);
} else {
ctx.fireChannelRead(msg);
}
}
}
@@ -267,7 +267,7 @@ public class FallbackBukkitInjector {
return;
}
} catch (Exception exception) {
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
Sonar.get0().getLogger().error("An error occurred while injecting {}", exception);
}
}
}
@@ -92,7 +92,7 @@ public final class BukkitJoinListener implements Listener {
channel.close();
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
Sonar.get0().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
}
}
}
@@ -86,6 +86,10 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
@Override
public void enable() {
// Add warning because of questionable design choices that haunt me in my dreams.
logger.warn("The use of BungeeCord is supported but not condoned.");
logger.warn("Consider upgrading to Velocity (https://velocitypowered.com)");
// Initialize bStats.org metrics
metrics = new Metrics(getPlugin(), getPlatform().getMetricsId());
@@ -38,7 +38,7 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
// Create our own invocation source wrapper to handle messages properly
final InvocationSource invocationSource = new InvocationSource(
sender instanceof ProxiedPlayer ? ((ProxiedPlayer) sender).getUniqueId() : null,
Sonar.get().sender(sender),
Sonar.get0().sender(sender),
sender::hasPermission);
// Pass the invocation source and command arguments to our command handler
handle(invocationSource, args);
@@ -20,7 +20,11 @@ package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import lombok.experimental.UtilityClass;
import lombok.val;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
import org.jetbrains.annotations.NotNull;
import sun.misc.Unsafe;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
@@ -34,26 +38,59 @@ import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_
public class FallbackBungeeInjector {
public void inject() {
try {
final Field childField = PipelineUtils.class.getField("SERVER_CHILD");
final Field childField;
try {
// This field only exists on BungeeCord build 1908 and below.
// See https://github.com/SpigotMC/BungeeCord/pull/3776 for more info.
//noinspection all
childField = PipelineUtils.class.getField("SERVER_CHILD");
} catch (NoSuchFieldException exception) {
injectOnModernBungeeCord();
return;
}
childField.setAccessible(true);
// Make sure to store the original channel initializer
final ChannelInitializer<Channel> originalInitializer = PipelineUtils.SERVER_CHILD;
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
final Object base = unsafe.staticFieldBase(childField);
final long offset = unsafe.staticFieldOffset(childField);
// Replace the original channel initializer with our new injected initializer
unsafe.putObject(base, offset, injectedInitializer);
injectOnOldBungeeCord(childField);
} catch (Exception exception) {
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
Sonar.get0().getLogger().error("An error occurred while injecting {}", exception);
}
}
private static void injectOnOldBungeeCord(final @NotNull Field childField) throws Exception {
// Make sure to store the original channel initializer
//noinspection unchecked
val originalInitializer = (ChannelInitializer<Channel>) childField.get(null);
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
final Object base = unsafe.staticFieldBase(childField);
final long offset = unsafe.staticFieldOffset(childField);
// Replace the original channel initializer with our new injected initializer
unsafe.putObject(base, offset, injectedInitializer);
}
// See https://github.com/SpigotMC/BungeeCord/pull/3787
private static void injectOnModernBungeeCord() {
// The order is important...
final BungeeChannelInitializer original = ProxyServer.getInstance().unsafe().getFrontendChannelInitializer();
final BungeeChannelInitializer newInitializer = BungeeChannelInitializer.create(
channel -> {
// https://github.com/SpigotMC/BungeeCord/pull/3787#issuecomment-2661059876
if (!original.getChannelAcceptor().accept(channel)) {
return false;
}
FallbackInjectedChannelInitializer.inject(channel,
pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
return true;
});
ProxyServer.getInstance().unsafe().setFrontendChannelInitializer(newInitializer);
}
}
+2 -2
View File
@@ -2,6 +2,6 @@ dependencies {
compileOnly(project(":api"))
compileOnly(rootProject.libs.imagefilters)
testCompileOnly(project(":api"))
testCompileOnly(rootProject.libs.imagefilters)
testImplementation(project(":api"))
testImplementation(rootProject.libs.imagefilters)
}
@@ -109,12 +109,12 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
// Randomize the colors for the gradient effect
for (int i = 0; i < COLORS.length; i++) {
final float random = 0.9f + RANDOM.nextFloat() * 0.1f;
COLORS[i] = Color.getHSBColor(RANDOM.nextFloat(), random, random);
COLORS[i] = Color.getHSBColor(RANDOM.nextFloat(), random * 0.8f, random);
}
// Apply the random gradient effect
graphics.setPaint(new LinearGradientPaint(0, 0, width, height,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REPEAT));
}
private void drawCharacters(final @NotNull Graphics2D graphics,
@@ -0,0 +1,212 @@
/*
* Copyright (C) 2025 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.captcha.legacy;
import com.jhlabs.image.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.filters.NoiseOverlayFilter;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class LegacyCaptchaGenerator implements CaptchaGenerator {
private static final ScratchFilter SCRATCHES = new ScratchFilter(4);
private static final NoiseOverlayFilter NOISE = new NoiseOverlayFilter(1, 7);
private static final SmearFilter SMEAR = new SmearFilter();
private static final SaturationFilter SATURATION = new SaturationFilter();
private static final RippleFilter RIPPLE = new RippleFilter();
private static final Random RANDOM = new Random();
static {
RIPPLE.setXAmplitude(0);
float yAmplitude = 10 - RANDOM.nextInt(20);
if (Math.abs(yAmplitude) < 5) {
yAmplitude = yAmplitude >= 0 ? 5 : -5;
}
RIPPLE.setYAmplitude(yAmplitude);
SATURATION.setAmount(0.45f + RANDOM.nextFloat() * 0.15f);
SMEAR.setShape(SmearFilter.CIRCLES);
SMEAR.setMix(0.15f);
SMEAR.setDensity(0.1f);
SMEAR.setDistance(5);
}
private final int width = 128, height = 128;
private final @Nullable File background;
private @Nullable BufferedImage backgroundImage;
private GradientPaint gradient;
public BufferedImage createCachedBackgroundImage() {
// Create a new image buffer with a 3-byte color spectrum
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
if (background == null) {
if (backgroundImage != null) {
return backgroundImage;
}
// Don't use any special background image
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Fill the entire background image with a noise texture
image = new CausticsFilter().filter(image, null);
// Adjust the saturation of the randomly generated background noise
final SaturationFilter saturationFilter = new SaturationFilter();
saturationFilter.setAmount(0.4f + RANDOM.nextFloat() * 0.2f);
image = saturationFilter.filter(image, null);
// Un-sharpen the background a bit
image = new UnsharpFilter().filter(image, null);
return image;
}
// Try to load the image from a file if it doesn't exist yet
if (backgroundImage == null) {
try {
backgroundImage = ImageIO.read(background);
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
image.createGraphics().drawImage(backgroundImage, 0, 0, Color.WHITE, null);
return image;
}
private @NotNull BufferedImage mergeImages(final @NotNull BufferedImage background,
final @NotNull BufferedImage foreground) {
// Get the background image and create a new foreground image
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
// Create a new image with transparency for the merged result
final Graphics2D graphics = image.createGraphics();
// Draw the foreground image on top of the background at specified coordinates
graphics.drawImage(background, 0, 0, null);
// Set AlphaComposite to handle transparency for the foreground image
graphics.setComposite(AlphaComposite.SrcOver);
// Draw the foreground image on top of the background at specified coordinates
graphics.drawImage(foreground, 0, 0, null);
graphics.dispose();
return image;
}
@Override
public @NotNull BufferedImage createImage(final char @NotNull [] answer) {
final Color color0 = Color.getHSBColor(RANDOM.nextFloat(), 1, 1);
final Color color1 = Color.getHSBColor(RANDOM.nextFloat(), 1, 0.5f);
gradient = new GradientPaint(0, 0, color0, width, height, color1);
// Get the background image and create a new foreground image
BufferedImage foreground = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphics = foreground.createGraphics();
final FontRenderContext ctx = graphics.getFontRenderContext();
// Change some rendering hints for anti aliasing
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw characters
drawCharacters(graphics, ctx, answer);
// Make sure to dispose the graphics after using it
graphics.dispose();
// Apply any given filter to the foreground
new BumpFilter().filter(foreground, foreground);
SMEAR.filter(foreground, foreground);
RIPPLE.filter(foreground, foreground);
SCRATCHES.filter(foreground, foreground);
final BufferedImage merged = mergeImages(createCachedBackgroundImage(), foreground);
NOISE.transform(merged);
return merged;
}
private void drawCharacters(final @NotNull Graphics2D graphics,
final @NotNull FontRenderContext ctx,
final char[] answer) {
// Create font render context
final int fontSize = 62 - answer.length * 2;
final Font font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
// Calculate string width
final double stringWidth = font.getStringBounds(answer, 0, answer.length, ctx).getWidth();
// Calculate character positions
int beginX = (int) ((width - stringWidth) / 2D + stringWidth / (answer.length * 2.5D));
final int beginY = (int) ((height + fontSize / 2D) / 2D);
double rotation = 0;
if (gradient != null) {
graphics.setPaint(gradient);
}
// Draw each character one by one
for (final char character : answer) {
// Create a glyph vector for the character
final GlyphVector glyphVector = font.createGlyphVector(ctx, String.valueOf(character));
final int visualBoundsWidth = (int) glyphVector.getVisualBounds().getWidth();
if (gradient == null) {
Objects.requireNonNull(backgroundImage);
// Use the inverse color by checking the background image
// Then, create a gradient for the text by using the colors
final int x0 = Math.min(Math.max(beginX + 5 /* small threshold */, 0), width);
final int x1 = Math.min(Math.max(beginX + (int) visualBoundsWidth, 0), width);
final int y = beginY + fontSize / 2;
final Color color0 = new Color(~backgroundImage.getRGB(x0, y));
final Color color1 = new Color(~backgroundImage.getRGB(x1, y));
final GradientPaint gradient = new GradientPaint(0, 0, color0, width, height, color1);
graphics.setPaint(gradient);
}
// Apply a transformation to the glyph vector using AffineTransform
final AffineTransform transformation = AffineTransform.getTranslateInstance(beginX, beginY);
// Add a bit of randomization to the rotation
rotation += Math.toRadians(6 - RANDOM.nextInt(12));
transformation.rotate(rotation);
transformation.scale(1, 1.25);
// Draw the glyph to the buffered image
final Shape transformedShape = transformation.createTransformedShape(glyphVector.getOutline());
graphics.fill(transformedShape);
// Add text outline/shadow to confuse an AI's edge detection
addTextOutline(graphics, transformedShape);
// Update next X position
beginX += visualBoundsWidth + 2;
}
}
private static void addTextOutline(final @NotNull Graphics2D graphics,
final @NotNull Shape transformedShape) {
// Create a stroked copy of the text and slightly offset/distort it
final Shape strokedShape = new BasicStroke().createStrokedShape(transformedShape);
final double tx = 0.5 + RANDOM.nextDouble();
final double ty = 0.5 + RANDOM.nextDouble();
// Draw the stroked shape
final AffineTransform transform = AffineTransform.getTranslateInstance(tx, ty);
graphics.fill(transform.createTransformedShape(strokedShape));
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 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.captcha.legacy;
import com.jhlabs.image.TransformFilter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public final class RippleFilter extends TransformFilter {
private float xAmplitude, yAmplitude;
@Override
protected void transformInverse(final int x, final int y, final float @NotNull [] out) {
final float nx = (float) y / 13f;
final float ny = (float) x / 13f;
final float fx = (float) Math.sin(nx);
final float fy = (float) Math.sin(ny);
out[0] = (float) x + xAmplitude * fx;
out[1] = (float) y + yAmplitude * fy;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2025 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.captcha.legacy;
import com.jhlabs.image.AbstractBufferedImageOp;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@RequiredArgsConstructor
public final class ScratchFilter extends AbstractBufferedImageOp {
private static final Random RANDOM = new Random();
private final int amount;
@Override
public @NotNull BufferedImage filter(final @NotNull BufferedImage src, final BufferedImage dst) {
final Graphics2D graphics = src.createGraphics();
// Apply some gradient effect on them
final Color color0 = Color.getHSBColor(RANDOM.nextFloat(), RANDOM.nextFloat(), 1);
final Color color1 = new Color(~color0.getRGB());
final GradientPaint gradient = new GradientPaint(0, 0, color0, src.getWidth(), src.getHeight(), color1);
graphics.setPaint(gradient);
final float halfWidth = src.getWidth() * 0.5f;
for (int i = 0; i < amount; ++i) {
final float randomX = src.getWidth() * RANDOM.nextFloat();
final float randomY = src.getHeight() * RANDOM.nextFloat();
final float amplitude = 6.2831855f * (RANDOM.nextFloat() - 0.5f);
final float sin = (float) Math.sin(amplitude) * halfWidth;
final float cos = (float) Math.cos(amplitude) * halfWidth;
final float x1 = randomX - cos;
final float y1 = randomY - sin;
final float x2 = randomX + cos;
final float y2 = randomY + sin;
graphics.drawLine((int) x1, (int) y1, (int) x2, (int) y2);
}
graphics.dispose();
return src;
}
}
@@ -47,7 +47,7 @@ class LibraryLoader {
Library.builder()
.groupId("com{}github{}ben-manes{}caffeine")
.artifactId("caffeine")
.version("3.1.8")
.version("3.2.0")
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
.build(),
// ORMLite
@@ -55,11 +55,11 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
// Account for this by checking if the inetAddress has been set yet
if (inetAddress != null) {
// Remove the IP address from the queue
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (ignored, action) -> {
Sonar.get0().getFallback().getQueue().getPlayers().compute(inetAddress, (ignored, action) -> {
// The player is not queued, so we need to remove them from other maps as well
if (action == null) {
// Remove the IP address from the connected players, if needed
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> {
Sonar.get0().getFallback().getConnected().compute(inetAddress, (__, v) -> {
/*
* Remove this account from the online players or decrement the number of accounts
* with the same IP, but only if the player is logging into the backend server.
@@ -67,7 +67,7 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
* we've never actually incremented it in the first place ¯\_(ツ)_/¯
*/
if (v == null) {
Sonar.get().getFallback().getOnline().compute(inetAddress,
Sonar.get0().getFallback().getOnline().compute(inetAddress,
(ignored1, count) -> count == null || count <= 1 ? null : count - 1);
}
return null;
@@ -47,7 +47,7 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
protected @Nullable String username;
protected ProtocolVersion protocolVersion;
protected RemovalListener channelRemovalListener = RemovalListener.EMPTY;
protected @Nullable RemovalListener channelRemovalListener;
/**
* Validates and handles incoming handshake packets
@@ -97,22 +97,22 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
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)) {
if (Sonar.get0().getFallback().getConnected().containsKey(inetAddress)) {
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())) {
if (Sonar.get0().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
return;
}
final String hostAddress = inetAddress.getHostAddress();
// Check if the player failed the verification too many times
final int limit = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
final int limit = Sonar.get0().getConfig().getVerification().getBlacklistThreshold();
if (limit > 0) {
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
final int score = Sonar.get0().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
@@ -120,27 +120,27 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
if (!Sonar.get0().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
if (geyser && !Sonar.get0().getConfig().getVerification().isCheckGeyser()) {
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)) {
if (Sonar.get0().getVerifiedPlayerController().getCache().contains(fingerprint)) {
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Check if the IP address is currently being rate-limited
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
if (!Sonar.get0().getFallback().getRatelimiter().attempt(inetAddress)) {
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -149,7 +149,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
rewriteProtocol(ctx, channelRemovalListener);
// Queue the connection for further processing
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
Sonar.get0().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
// Check if the player is already queued since we don't want bots to flood the queue
if (runnable != null) {
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
@@ -168,8 +168,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
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();
Sonar.get0().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get0().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
@@ -191,7 +191,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
* Removes all pipelines and rewrites them using our custom handlers
*/
private static void rewriteProtocol(final @NotNull ChannelHandlerContext ctx,
final @NotNull RemovalListener removalListener) {
final @Nullable RemovalListener removalListener) {
for (final Map.Entry<String, ChannelHandler> entry : ctx.pipeline()) {
// Don't accidentally remove Sonar's handlers
if (entry.getKey().startsWith("sonar")
@@ -202,12 +202,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
continue;
}
ctx.pipeline().remove(entry.getValue());
removalListener.accept(ctx.pipeline(), entry.getKey(), entry.getValue());
if (removalListener != null) {
removalListener.accept(ctx.pipeline(), entry.getKey(), entry.getValue());
}
}
// Add our read/write timeout handler
ctx.pipeline().addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
Sonar.get().getConfig().getVerification().getReadTimeout(),
Sonar.get().getConfig().getVerification().getWriteTimeout(),
Sonar.get0().getConfig().getVerification().getReadTimeout(),
Sonar.get0().getConfig().getVerification().getWriteTimeout(),
TimeUnit.MILLISECONDS));
}
@@ -231,11 +233,11 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
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();
final String handler = Sonar.get0().getPlatform().getConnectionHandler();
if (channel.pipeline().context(handler) != null) {
channel.pipeline().remove(handler);
}
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
final String encoder = Sonar.get0().getPlatform().getEncoder().apply(channel.pipeline());
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
// Close the channel if no encoder exists
if (currentEncoder != null) {
@@ -256,7 +258,5 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
void accept(final @NotNull ChannelPipeline pipeline,
final @NotNull String name,
final @NotNull ChannelHandler handler);
RemovalListener EMPTY = (pipeline, name, handler) -> {};
}
}
@@ -76,18 +76,18 @@ public final class FallbackUserWrapper implements FallbackUser {
GlobalSonarStatistics.totalAttemptedVerifications++;
if (Sonar.get().getConfig().getVerification().isLogConnections()
&& (Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack())) {
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
if (Sonar.get0().getConfig().getVerification().isLogConnections()
&& (Sonar.get0().getAttackTracker().getCurrentAttack() == null
|| Sonar.get0().getConfig().getVerification().isLogDuringAttack())) {
Sonar.get0().getLogger().info(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.logs.connection")
.replace("<username>", username)
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
.replace("<ip>", Sonar.get0().getConfig().formatAddress(inetAddress))
.replace("<protocol>", protocolVersion.getName()));
}
// Call the VerifyJoinEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(this));
Sonar.get0().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
@@ -96,8 +96,14 @@ public final class FallbackUserWrapper implements FallbackUser {
return;
}
// How? Is there some kind of de-sync or race condition?
if (channel.pipeline().context(FALLBACK_FRAME_ENCODER) != null) {
channel.close(); // Nope ¯\_(ツ)_/¯
return;
}
// Mark the player as connected by caching them in a map of verifying players
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
Sonar.get0().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
@@ -60,13 +60,17 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
throw new ReflectiveOperationException(throwable);
}
inject(channel, sonarPipelineInjector);
}
public static void inject(final @NotNull Channel channel, final Consumer<ChannelPipeline> sonarPipelineInjector) {
// Inject Sonar's channel handler into the pipeline;
// Also make sure the player is not a fake player to avoid compatibility issues
if (channel.isActive() && !FakeChannelUtil.isFakeChannel(channel)) {
final FallbackInboundHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
// We need to be careful on Bukkit, as the encoder can be different
if (Sonar.get().getPlatform() == SonarPlatform.BUKKIT) {
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
if (Sonar.get0().getPlatform() == SonarPlatform.BUKKIT) {
final String encoder = Sonar.get0().getPlatform().getEncoder().apply(channel.pipeline());
channel.pipeline().addBefore(encoder, FALLBACK_INBOUND_HANDLER, inboundHandler);
} else {
channel.pipeline().addFirst(FALLBACK_INBOUND_HANDLER, inboundHandler);
@@ -44,12 +44,12 @@ public class CaptchaPreparer {
}
final SystemTimer timer = new SystemTimer();
Sonar.get().getLogger().info("Asynchronously preparing CAPTCHA answers...");
Sonar.get().getLogger().info("Players will be able to join even if the preparation isn't finished");
Sonar.get0().getLogger().info("Asynchronously preparing CAPTCHA answers...");
Sonar.get0().getLogger().info("Players will be able to join even if the preparation isn't finished");
// Prepare cache
final int precomputeAmount = Sonar.get().getConfig().getVerification().getMap().getPrecomputeAmount();
final char[] alphabet = Sonar.get().getConfig().getVerification().getMap().getAlphabet().toCharArray();
final int precomputeAmount = Sonar.get0().getConfig().getVerification().getMap().getPrecomputeAmount();
final char[] alphabet = Sonar.get0().getConfig().getVerification().getMap().getAlphabet().toCharArray();
preparedAmount = 0;
cached = new MapCaptchaInfo[precomputeAmount];
@@ -62,13 +62,13 @@ public class CaptchaPreparer {
for (int j = 0; j < answer.length; j++) {
answer[j] = alphabet[RANDOM.nextInt(alphabet.length)];
}
final BufferedImage image = Sonar.get().getFallback().getCaptchaGenerator().createImage(answer);
final BufferedImage image = Sonar.get0().getFallback().getCaptchaGenerator().createImage(answer);
// Convert and cache converted Minecraft map bytes
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), MapColorPalette.imageToBuffer(image));
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
Sonar.get().getEventManager().publish(new CaptchaGenerationEndEvent(timer, preparedAmount));
Sonar.get0().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
Sonar.get0().getEventManager().publish(new CaptchaGenerationEndEvent(timer, preparedAmount));
});
}
@@ -66,7 +66,7 @@ public enum FallbackPacketRegistry {
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x01, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_20_5, true));
clientbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
clientbound.register(FinishConfigurationPacket.class, () -> FinishConfigurationPacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, true),
map(0x03, MINECRAFT_1_20_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
@@ -81,7 +81,7 @@ public enum FallbackPacketRegistry {
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false),
map(0x02, MINECRAFT_1_20_5, false));
serverbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
serverbound.register(FinishConfigurationPacket.class, () -> FinishConfigurationPacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
@@ -91,6 +91,7 @@ public enum FallbackPacketRegistry {
},
GAME {
{
// minecraft:login
clientbound.register(JoinGamePacket.class, JoinGamePacket::new,
map(0x01, MINECRAFT_1_7_2, true),
map(0x23, MINECRAFT_1_9, true),
@@ -106,7 +107,9 @@ public enum FallbackPacketRegistry {
map(0x28, MINECRAFT_1_19_4, true),
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true),
map(0x2C, MINECRAFT_1_21_2, true));
map(0x2C, MINECRAFT_1_21_2, true),
map(0x2B, MINECRAFT_1_21_5, true));
// minecraft:keep_alive
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, true),
map(0x1F, MINECRAFT_1_9, true),
@@ -122,7 +125,9 @@ public enum FallbackPacketRegistry {
map(0x23, MINECRAFT_1_19_4, true),
map(0x24, MINECRAFT_1_20_2, true),
map(0x26, MINECRAFT_1_20_5, true),
map(0x27, MINECRAFT_1_21_2, true));
map(0x27, MINECRAFT_1_21_2, true),
map(0x26, MINECRAFT_1_21_5, true));
// minecraft:disconnect
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
map(0x1A, MINECRAFT_1_9, true),
@@ -137,7 +142,9 @@ public enum FallbackPacketRegistry {
map(0x17, MINECRAFT_1_19_3, true),
map(0x1A, MINECRAFT_1_19_4, true),
map(0x1B, MINECRAFT_1_20_2, true),
map(0x1D, MINECRAFT_1_20_5, true));
map(0x1D, MINECRAFT_1_20_5, true),
map(0x1C, MINECRAFT_1_21_5, true));
// minecraft:player_position
clientbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x08, MINECRAFT_1_7_2, true),
map(0x2E, MINECRAFT_1_9, true),
@@ -154,7 +161,9 @@ public enum FallbackPacketRegistry {
map(0x3C, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_20_2, true),
map(0x40, MINECRAFT_1_20_5, true),
map(0x42, MINECRAFT_1_21_2, true));
map(0x42, MINECRAFT_1_21_2, true),
map(0x41, MINECRAFT_1_21_5, true));
// minecraft:player_abilities
clientbound.register(PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new,
map(0x39, MINECRAFT_1_7_2, true),
map(0x2B, MINECRAFT_1_9, true),
@@ -171,7 +180,9 @@ public enum FallbackPacketRegistry {
map(0x34, MINECRAFT_1_19_4, true),
map(0x36, MINECRAFT_1_20_2, true),
map(0x38, MINECRAFT_1_20_5, true),
map(0x3A, MINECRAFT_1_21_2, true));
map(0x3A, MINECRAFT_1_21_2, true),
map(0x39, MINECRAFT_1_21_5, true));
// minecraft:level_chunk_with_light
clientbound.register(ChunkDataPacket.class, ChunkDataPacket::new,
map(0x21, MINECRAFT_1_7_2, true),
map(0x20, MINECRAFT_1_9, true),
@@ -187,7 +198,9 @@ public enum FallbackPacketRegistry {
map(0x24, MINECRAFT_1_19_4, true),
map(0x25, MINECRAFT_1_20_2, true),
map(0x27, MINECRAFT_1_20_5, true),
map(0x28, MINECRAFT_1_21_2, true));
map(0x28, MINECRAFT_1_21_2, true),
map(0x27, MINECRAFT_1_21_5, true));
// minecraft:remove_entities
clientbound.register(RemoveEntitiesPacket.class, RemoveEntitiesPacket::new,
map(0x13, MINECRAFT_1_7_2, true),
map(0x30, MINECRAFT_1_9, true),
@@ -206,7 +219,9 @@ public enum FallbackPacketRegistry {
map(0x3E, MINECRAFT_1_19_4, true),
map(0x40, MINECRAFT_1_20_2, true),
map(0x42, MINECRAFT_1_20_5, true),
map(0x47, MINECRAFT_1_21_2, true));
map(0x47, MINECRAFT_1_21_2, true),
map(0x46, MINECRAFT_1_21_5, true));
// minecraft:set_passengers
clientbound.register(SetPassengersPacket.class, SetPassengersPacket::new,
map(0x1B, MINECRAFT_1_7_2, true),
map(0x40, MINECRAFT_1_9, true),
@@ -222,26 +237,14 @@ public enum FallbackPacketRegistry {
map(0x5B, MINECRAFT_1_20_2, true),
map(0x5D, MINECRAFT_1_20_3, 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),
map(0x2C, MINECRAFT_1_14, true),
map(0x2D, MINECRAFT_1_15, true),
map(0x2C, MINECRAFT_1_16, true),
map(0x2B, MINECRAFT_1_16_2, true),
map(0x2C, MINECRAFT_1_17, true),
map(0x29, MINECRAFT_1_19, true),
map(0x2B, MINECRAFT_1_19_1, true),
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(0x33, MINECRAFT_1_21_2, true));
map(0x65, MINECRAFT_1_21_2, true),
map(0x64, MINECRAFT_1_21_5, true));
// minecraft:add_entity
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
map(0x01, MINECRAFT_1_19_4, true));
// minecraft:section_blocks_update
clientbound.register(UpdateSectionBlocksPacket.class, UpdateSectionBlocksPacket::new,
map(0x22, MINECRAFT_1_7_2, true),
map(0x10, MINECRAFT_1_9, true),
@@ -257,7 +260,9 @@ public enum FallbackPacketRegistry {
map(0x45, MINECRAFT_1_20_2, true),
map(0x47, MINECRAFT_1_20_3, true),
map(0x49, MINECRAFT_1_20_5, true),
map(0x4E, MINECRAFT_1_21_2, true));
map(0x4E, MINECRAFT_1_21_2, true),
map(0x4D, MINECRAFT_1_21_5, true));
// minecraft:ping
clientbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x32, MINECRAFT_1_7_2, true),
map(0x11, MINECRAFT_1_9, true),
@@ -272,7 +277,9 @@ public enum FallbackPacketRegistry {
map(0x32, MINECRAFT_1_19_4, true),
map(0x33, MINECRAFT_1_20_2, true),
map(0x35, MINECRAFT_1_20_5, true),
map(0x37, MINECRAFT_1_21_2, true));
map(0x37, MINECRAFT_1_21_2, true),
map(0x36, MINECRAFT_1_21_5, true));
// minecraft:set_default_spawn_position
clientbound.register(SetDefaultSpawnPositionPacket.class, SetDefaultSpawnPositionPacket::new,
map(0x05, MINECRAFT_1_7_2, true),
map(0x43, MINECRAFT_1_9, true),
@@ -290,7 +297,9 @@ public enum FallbackPacketRegistry {
map(0x52, MINECRAFT_1_20_2, true),
map(0x54, MINECRAFT_1_20_3, true),
map(0x56, MINECRAFT_1_20_5, true),
map(0x5B, MINECRAFT_1_21_2, true));
map(0x5B, MINECRAFT_1_21_2, true),
map(0x5A, MINECRAFT_1_21_5, true));
// minecraft:map_item_data
clientbound.register(MapDataPacket.class, MapDataPacket::new,
map(0x34, MINECRAFT_1_7_2, true),
map(0x24, MINECRAFT_1_9, true),
@@ -305,7 +314,9 @@ public enum FallbackPacketRegistry {
map(0x29, MINECRAFT_1_19_4, true),
map(0x2A, MINECRAFT_1_20_2, true),
map(0x2C, MINECRAFT_1_20_5, true),
map(0x2D, MINECRAFT_1_21_2, true));
map(0x2D, MINECRAFT_1_21_2, true),
map(0x2C, MINECRAFT_1_21_5, true));
// minecraft:container_set_slot
clientbound.register(SetContainerSlotPacket.class, SetContainerSlotPacket::new,
map(0x2F, MINECRAFT_1_7_2, true),
map(0x16, MINECRAFT_1_9, true),
@@ -318,7 +329,9 @@ public enum FallbackPacketRegistry {
map(0x13, MINECRAFT_1_19, true),
map(0x12, MINECRAFT_1_19_3, true),
map(0x14, MINECRAFT_1_19_4, true),
map(0x15, MINECRAFT_1_20_2, true));
map(0x15, MINECRAFT_1_20_2, true),
map(0x14, MINECRAFT_1_21_5, true));
// minecraft:set_experience
clientbound.register(SetExperiencePacket.class, SetExperiencePacket::new,
map(0x1F, MINECRAFT_1_7_2, true),
map(0x3D, MINECRAFT_1_9, true),
@@ -334,7 +347,9 @@ public enum FallbackPacketRegistry {
map(0x58, MINECRAFT_1_20_2, true),
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true),
map(0x61, MINECRAFT_1_21_2, true));
map(0x61, MINECRAFT_1_21_2, true),
map(0x60, MINECRAFT_1_21_5, true));
// minecraft:system_chat
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
@@ -349,14 +364,19 @@ public enum FallbackPacketRegistry {
map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true));
map(0x73, MINECRAFT_1_21_2, true),
map(0x72, MINECRAFT_1_21_5, true));
// minecraft:game_event
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true),
map(0x23, MINECRAFT_1_21_2, true));
map(0x23, MINECRAFT_1_21_2, true),
map(0x22, MINECRAFT_1_21_5, true));
// minecraft:transfer
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, true),
map(0x7A, MINECRAFT_1_21_2, true));
// minecraft:set_held_slot
clientbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, true),
map(0x37, MINECRAFT_1_9, true),
@@ -374,7 +394,9 @@ public enum FallbackPacketRegistry {
map(0x4F, MINECRAFT_1_20_2, true),
map(0x51, MINECRAFT_1_20_3, true),
map(0x53, MINECRAFT_1_20_5, true),
map(0x63, MINECRAFT_1_21_2, true));
map(0x63, MINECRAFT_1_21_2, true),
map(0x62, MINECRAFT_1_21_5, true));
// minecraft:animate
clientbound.register(EntityAnimationPacket.class, EntityAnimationPacket::new,
map(0x0B, MINECRAFT_1_7_2, true),
map(0x06, MINECRAFT_1_9, true),
@@ -382,7 +404,9 @@ public enum FallbackPacketRegistry {
map(0x06, MINECRAFT_1_17, true),
map(0x03, MINECRAFT_1_19, true),
map(0x04, MINECRAFT_1_19_4, true),
map(0x03, MINECRAFT_1_20_2, true));
map(0x03, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_21_5, true));
// minecraft:set_time
clientbound.register(UpdateTimePacket.class, UpdateTimePacket::new,
map(0x03, MINECRAFT_1_7_2, true),
map(0x44, MINECRAFT_1_9, true),
@@ -400,10 +424,14 @@ public enum FallbackPacketRegistry {
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));
map(0x6B, MINECRAFT_1_21_2, true),
map(0x6A, MINECRAFT_1_21_5, true));
// minecraft:client_tick_end
serverbound.register(ClientTickEndPacket.class, ClientTickEndPacket::new,
map(0x0B, MINECRAFT_1_21_2, false));
map(0x0B, MINECRAFT_1_21_2, false),
map(0x0C, MINECRAFT_1_21_6, false));
// minecraft:set_carried_item
serverbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
map(0x09, MINECRAFT_1_7_2, false),
map(0x17, MINECRAFT_1_9, false),
@@ -418,7 +446,9 @@ public enum FallbackPacketRegistry {
map(0x2C, MINECRAFT_1_20_3, false),
map(0x2F, MINECRAFT_1_20_5, false),
map(0x31, MINECRAFT_1_21_2, false),
map(0x33, MINECRAFT_1_21_4, false));
map(0x33, MINECRAFT_1_21_4, false),
map(0x34, MINECRAFT_1_21_6, false));
// minecraft:paddle_boat
serverbound.register(PaddleBoatPacket.class, PaddleBoatPacket::new,
map(0x11, MINECRAFT_1_9, false),
map(0x12, MINECRAFT_1_12, false),
@@ -434,7 +464,9 @@ public enum FallbackPacketRegistry {
map(0x1B, MINECRAFT_1_20_2, false),
map(0x1C, MINECRAFT_1_20_3, false),
map(0x1F, MINECRAFT_1_20_5, false),
map(0x21, MINECRAFT_1_21_2, false));
map(0x21, MINECRAFT_1_21_2, false),
map(0x22, MINECRAFT_1_21_6, false));
// minecraft:player_input
serverbound.register(PlayerInputPacket.class, PlayerInputPacket::new,
map(0x0C, MINECRAFT_1_7_2, false),
map(0x15, MINECRAFT_1_9, false),
@@ -451,7 +483,9 @@ public enum FallbackPacketRegistry {
map(0x23, MINECRAFT_1_20_3, false),
map(0x26, MINECRAFT_1_20_5, false),
map(0x28, MINECRAFT_1_21_2, false),
map(0x29, MINECRAFT_1_21_4, false));
map(0x29, MINECRAFT_1_21_4, false),
map(0x2A, MINECRAFT_1_21_6, false));
// minecraft:move_vehicle
serverbound.register(VehicleMovePacket.class, VehicleMovePacket::new,
map(0x10, MINECRAFT_1_9, false),
map(0x11, MINECRAFT_1_12, false),
@@ -467,7 +501,9 @@ public enum FallbackPacketRegistry {
map(0x1A, MINECRAFT_1_20_2, false),
map(0x1B, MINECRAFT_1_20_3, false),
map(0x1E, MINECRAFT_1_20_5, false),
map(0x20, MINECRAFT_1_21_2, false));
map(0x20, MINECRAFT_1_21_2, false),
map(0x21, MINECRAFT_1_21_6, false));
// minecraft:chat
serverbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
@@ -478,7 +514,9 @@ public enum FallbackPacketRegistry {
map(0x05, MINECRAFT_1_19_1, false),
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false));
map(0x07, MINECRAFT_1_21_2, false),
map(0x08, MINECRAFT_1_21_6, false));
// minecraft:keep_alive
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
@@ -495,7 +533,9 @@ public enum FallbackPacketRegistry {
map(0x14, MINECRAFT_1_20_2, false),
map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false),
map(0x1A, MINECRAFT_1_21_2, false));
map(0x1A, MINECRAFT_1_21_2, false),
map(0x1B, MINECRAFT_1_21_6, false));
// minecraft:client_information
serverbound.register(ClientInformationPacket.class, ClientInformationPacket::new,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
@@ -508,7 +548,9 @@ public enum FallbackPacketRegistry {
map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false),
map(0x0C, MINECRAFT_1_21_2, false));
map(0x0C, MINECRAFT_1_21_2, false),
map(0x0D, MINECRAFT_1_21_6, false));
// minecraft:custom_payload
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
@@ -523,7 +565,9 @@ public enum FallbackPacketRegistry {
map(0x0D, MINECRAFT_1_19_4, false),
map(0x0F, MINECRAFT_1_20_2, false),
map(0x12, MINECRAFT_1_21, false),
map(0x14, MINECRAFT_1_21_2, false));
map(0x14, MINECRAFT_1_21_2, false),
map(0x15, MINECRAFT_1_21_6, false));
// minecraft:move_player_status_only
serverbound.register(SetPlayerOnGround.class, SetPlayerOnGround::new,
map(0x03, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
@@ -540,7 +584,9 @@ public enum FallbackPacketRegistry {
map(0x19, MINECRAFT_1_20_2, false),
map(0x1A, MINECRAFT_1_20_3, false),
map(0x1D, MINECRAFT_1_20_5, false),
map(0x1F, MINECRAFT_1_21_2, false));
map(0x1F, MINECRAFT_1_21_2, false),
map(0x20, MINECRAFT_1_21_6, false));
// minecraft:move_player_pos
serverbound.register(SetPlayerPositionPacket.class, SetPlayerPositionPacket::new,
map(0x04, MINECRAFT_1_7_2, false),
map(0x0C, MINECRAFT_1_9, false),
@@ -557,7 +603,9 @@ public enum FallbackPacketRegistry {
map(0x16, MINECRAFT_1_20_2, false),
map(0x17, MINECRAFT_1_20_3, false),
map(0x1A, MINECRAFT_1_20_5, false),
map(0x1C, MINECRAFT_1_21_2, false));
map(0x1C, MINECRAFT_1_21_2, false),
map(0x1D, MINECRAFT_1_21_6, false));
// minecraft:move_player_rot
serverbound.register(SetPlayerRotationPacket.class, SetPlayerRotationPacket::new,
map(0x05, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
@@ -574,7 +622,9 @@ public enum FallbackPacketRegistry {
map(0x18, MINECRAFT_1_20_2, false),
map(0x19, MINECRAFT_1_20_3, false),
map(0x1C, MINECRAFT_1_20_5, false),
map(0x1E, MINECRAFT_1_21_2, false));
map(0x1E, MINECRAFT_1_21_2, false),
map(0x1F, MINECRAFT_1_21_6, false));
// minecraft:move_player_pos_rot
serverbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x06, MINECRAFT_1_7_2, false),
map(0x0D, MINECRAFT_1_9, false),
@@ -591,9 +641,12 @@ public enum FallbackPacketRegistry {
map(0x17, MINECRAFT_1_20_2, false),
map(0x18, MINECRAFT_1_20_3, false),
map(0x1B, MINECRAFT_1_20_5, false),
map(0x1D, MINECRAFT_1_21_2, false));
map(0x1D, MINECRAFT_1_21_2, false),
map(0x1E, MINECRAFT_1_21_6, false));
// minecraft:accept_teleportation
serverbound.register(ConfirmTeleportationPacket.class, ConfirmTeleportationPacket::new,
map(0x00, MINECRAFT_1_9, false));
// minecraft:pong
serverbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x0F, MINECRAFT_1_7_2, false),
map(0x05, MINECRAFT_1_9, false),
@@ -610,7 +663,9 @@ public enum FallbackPacketRegistry {
map(0x24, MINECRAFT_1_20_3, false),
map(0x27, MINECRAFT_1_20_5, false),
map(0x29, MINECRAFT_1_21_2, false),
map(0x2B, MINECRAFT_1_21_4, false));
map(0x2B, MINECRAFT_1_21_4, false),
map(0x2C, MINECRAFT_1_21_6, false));
// minecraft:swing
serverbound.register(AnimationPacket.class, AnimationPacket::new,
map(0x0A, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
@@ -625,7 +680,9 @@ public enum FallbackPacketRegistry {
map(0x33, MINECRAFT_1_20_3, false),
map(0x36, MINECRAFT_1_20_5, false),
map(0x38, MINECRAFT_1_21_2, false),
map(0x3A, MINECRAFT_1_21_4, false));
map(0x3A, MINECRAFT_1_21_4, false),
map(0x3B, MINECRAFT_1_21_5, false),
map(0x3C, MINECRAFT_1_21_6, false));
}
};
@@ -50,7 +50,7 @@ public final class FallbackPacketSnapshot implements FallbackPacket {
try {
originalPacket.encode(byteBuf, protocolVersion);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not encode packet {} for version {}: {}",
Sonar.get0().getLogger().error("Could not encode packet {} for version {}: {}",
originalPacket, protocolVersion, throwable);
break;
}
@@ -28,18 +28,19 @@ 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.captcha.legacy.LegacyCaptchaGenerator;
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.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;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
import java.io.File;
import java.util.Random;
import java.util.UUID;
@@ -75,8 +76,18 @@ public class FallbackPreparer {
public final FallbackPacket NO_MOVE_ABILITIES_BEDROCK = new PlayerAbilitiesPacket(0x06, 0, 0);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
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();
// I think the people working at Microsoft are actually the *real* ultimate trolls of the internet!
public final FallbackPacket[] EMPTY_CHUNK_DATA = new FallbackPacket[]{
new FallbackPacketSnapshot(new ChunkDataPacket(0, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(0, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(0, -1)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, -1)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, -1))
};
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))};
@@ -84,6 +95,7 @@ public class FallbackPreparer {
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[] REGISTRY_SYNC_1_21_4 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_4);
public final FallbackPacket[] REGISTRY_SYNC_1_21_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_5);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
@@ -119,15 +131,15 @@ public class FallbackPreparer {
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@SuppressWarnings("ResultOfMethodCallIgnored")
public void prepare() {
// Preload the packet registry to avoid CPU/RAM issues on 1st connection
Sonar.get().getLogger().info("Preloading all registered packets...");
Sonar.get0().getLogger().info("Preloading all registered packets...");
//noinspection ResultOfMethodCallIgnored
FallbackPacketRegistry.values();
// Prepare LoginSuccess packet with capped username to 16 characters
final UUID uuid = UUID.randomUUID();
String username = Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username");
String username = Sonar.get0().getConfig().getGeneralConfig().getString("verification.cached-username");
if (username.length() > 16) {
username = username.substring(0, 16);
}
@@ -135,16 +147,16 @@ public class FallbackPreparer {
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(),
Sonar.get0().getConfig().getVerification().getGamemode().getId(),
-1, 0, 0,
RANDOM.nextInt(3), 1, 0, 0,
new String[]{"lol"}, "lol", "flat",
new String[]{"minecraft:overworld"}, "minecraft:overworld", "flat",
DimensionType.OVERWORLD, RANDOM.nextLong() & 1337,
false, true, false,
false, false, false, true));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
maxMovementTick = Sonar.get0().getConfig().getVerification().getGravity().getMaxMovementTicks();
double motionY = 0, fallDistance = 0;
@@ -184,25 +196,25 @@ public class FallbackPreparer {
}
// Prepare disconnect packets during login
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));
blacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getTooFastReconnect(), true));
unsupportedVersion = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getUnsupportedVersion(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getTooManyOnlinePerIp(), true));
// Prepare transfer packet
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
if (Sonar.get0().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
transferToOrigin = new FallbackPacketSnapshot(new TransferPacket(
Sonar.get().getConfig().getGeneralConfig().getString("verification.transfer.destination-host"),
Sonar.get().getConfig().getGeneralConfig().getInt("verification.transfer.destination-port")));
Sonar.get0().getConfig().getGeneralConfig().getString("verification.transfer.destination-host"),
Sonar.get0().getConfig().getGeneralConfig().getInt("verification.transfer.destination-port")));
} else {
transferToOrigin = null;
}
// Prepare update time packet
final int timeOfDay = Sonar.get().getConfig().getVerification().getTimeOfDay();
final int timeOfDay = Sonar.get0().getConfig().getVerification().getTimeOfDay();
if (timeOfDay != 1000) {
updateTime = new UpdateTimePacket(0L, timeOfDay, false);
} else {
@@ -210,29 +222,29 @@ public class FallbackPreparer {
}
// 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");
final String welcome = Sonar.get0().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())))));
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
if (Sonar.get0().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get0().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
// Prepare CAPTCHA messages
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
incorrectCaptcha = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
// Prepare countdown
xpCountdown = new FallbackPacket[Sonar.get().getConfig().getVerification().getMap().getMaxDuration() / 1000];
xpCountdown = new FallbackPacket[Sonar.get0().getConfig().getVerification().getMap().getMaxDuration() / 1000];
for (int i = 0; i < xpCountdown.length; i++) {
final float bar = (float) i / xpCountdown.length;
@@ -241,15 +253,19 @@ public class FallbackPreparer {
// Update the CAPTCHA generator if necessary
final CaptchaGenerationStartEvent generationStartEvent = new CaptchaGenerationStartEvent(
Sonar.get().getFallback().getCaptchaGenerator());
Sonar.get().getEventManager().publish(generationStartEvent);
Sonar.get0().getFallback().getCaptchaGenerator());
Sonar.get0().getEventManager().publish(generationStartEvent);
if (generationStartEvent.getCaptchaGenerator() == null
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator
|| generationStartEvent.getCaptchaGenerator() instanceof LegacyCaptchaGenerator) {
final File backgroundImage = Sonar.get0().getConfig().getVerification().getMap().getBackgroundImage();
final boolean useLegacyCaptchaGenerator = Sonar.get0().getConfig().getGeneralConfig()
.getString("verification.checks.map-captcha.style").equalsIgnoreCase("legacy");
Sonar.get0().getFallback().setCaptchaGenerator(useLegacyCaptchaGenerator
? new LegacyCaptchaGenerator(backgroundImage) : new StandardCaptchaGenerator(backgroundImage));
} else {
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
Sonar.get0().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare CAPTCHA answers
@@ -259,18 +275,20 @@ public class FallbackPreparer {
enterCodeMessage = null;
incorrectCaptcha = null;
xpCountdown = null;
Sonar.get().getFallback().setCaptchaGenerator(null);
Sonar.get0().getFallback().setCaptchaGenerator(null);
}
maxTotalPacketsSent = maxMovementTick + 2
+ (xpCountdown == null ? 0 : xpCountdown.length) * 20 + 5
+ Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get().getConfig().getVerification().getMap().getMaxTries()
+ Sonar.get0().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get0().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_4)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
return REGISTRY_SYNC_1_21_5;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_4)) {
return REGISTRY_SYNC_1_21_4;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
return REGISTRY_SYNC_1_21_2;
@@ -72,7 +72,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 7619;
}
return 8163;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8163;
}
return 8173;
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
@@ -118,7 +121,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 10749;
}
return 11293;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11293;
}
return 11303;
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -157,7 +163,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 7644;
}
return 8185;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8185;
}
return 8195;
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -199,7 +208,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 9462;
}
return 10006;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 10006;
}
return 10016;
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -238,7 +250,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 8152;
}
return 8696;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8696;
}
return 8706;
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -280,7 +295,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 11566;
}
return 12110;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 12110;
}
return 12120;
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -322,7 +340,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 11063;
}
return 11607;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11607;
}
return 11617;
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> id;
@@ -39,6 +39,7 @@ public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_21;
public final CompoundBinaryTag CODEC_1_21_2;
public final CompoundBinaryTag CODEC_1_21_4;
public final CompoundBinaryTag CODEC_1_21_5;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
@@ -52,13 +53,14 @@ public final class DimensionRegistry {
CODEC_1_21 = getCodec("codec_1_21.nbt");
CODEC_1_21_2 = getCodec("codec_1_21_2.nbt");
CODEC_1_21_4 = getCodec("codec_1_21_4.nbt");
CODEC_1_21_5 = getCodec("codec_1_21_5.nbt");
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
try (final InputStream inputStream = Sonar.class.getResourceAsStream("/assets/codecs/" + fileName)) {
return BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(inputStream), BinaryTagIO.Compression.GZIP);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not load mappings for {}: {}", fileName, throwable);
Sonar.get0().getLogger().error("Could not load mappings for {}: {}", fileName, throwable);
throw new IllegalStateException(throwable);
}
}
@@ -24,7 +24,9 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum DimensionType {
OVERWORLD("minecraft:overworld", 0, 0),
@Deprecated
THE_NETHER("minecraft:the_nether", -1, 1),
@Deprecated
THE_END("minecraft:the_end", 1, 2);
private final String key;
@@ -58,7 +58,10 @@ public enum EntityType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 85;
}
return 84;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 84;
}
return 85;
}),
MINECART(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_13_2)) {
@@ -94,7 +97,10 @@ public enum EntityType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 82;
}
return 81;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 81;
}
return 82;
});
private final Function<ProtocolVersion, Integer> id;
@@ -73,8 +73,22 @@ public enum ItemType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 1022;
}
return 1031;
}, protocolVersion -> protocolVersion.greaterThanOrEquals(MINECRAFT_1_21_2) ? 36 : 26);
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 1031;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_5)) {
return 1042;
}
return 1059;
}, protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 26;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 36;
}
return 37;
});
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Integer> components;
@@ -18,10 +18,14 @@
package xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration;
import io.netty.buffer.ByteBuf;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class FinishConfigurationPacket implements FallbackPacket {
public static final FinishConfigurationPacket INSTANCE = new FinishConfigurationPacket();
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
@@ -60,15 +60,26 @@ public final class ChunkDataPacket implements FallbackPacket {
}
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.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
// In 1.21.5. They changed to List<EnumMap<Heightmap.Type, long[]>>
ProtocolUtil.writeVarInt(byteBuf, 1); // List size
ProtocolUtil.writeVarInt(byteBuf, 4); // Ordinal of MOTION_BLOCKING
// Write long array
ProtocolUtil.writeVarInt(byteBuf, 37);
for (int i = 0; i < 37; i++) {
byteBuf.writeLong(0);
}
} else { // Nbt for older version
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();
ProtocolUtil.writeBinaryTag(byteBuf, protocolVersion, rootTag);
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)) {
@@ -95,7 +106,12 @@ public final class ChunkDataPacket implements FallbackPacket {
} else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtil.writeVarInt(byteBuf, 0);
} else {
final byte[] sectionData = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
// nonEmptyBlockCount. short occupies 2 bytes.
// SingularPalette (byte) + id (varint) + long array with size (older)
// Since 1.21.5. The length of the long array of PaletteStorage no longer depends on the VarInt in the packet
// SingularPalette doesn't need to read any additional long array.
// So we'll remove suffix 0 as array length here. The cost of writing a palette has been reduced from 3 to 2 bytes.
final byte[] sectionData = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_5) ? new byte[]{0, 0, 0, 0, 0, 0, 1, 0} : new byte[]{0, 0, 0, 0, 0, 1};
int count = 24;
ProtocolUtil.writeVarInt(byteBuf, sectionData.length * count);
@@ -67,6 +67,6 @@ public final class PluginMessagePacket implements FallbackPacket {
@Override
public int expectedMaxLength(final ProtocolVersion protocolVersion) {
return 0xFFF; // strict size limit
return Short.MAX_VALUE; // vanilla size limit
}
}
@@ -118,6 +118,10 @@ public final class SystemChatPacket implements FallbackPacket {
ProtocolUtil.readVarInt(byteBuf);
final byte[] bytes = new byte[DIV_FLOOR];
byteBuf.readBytes(bytes);
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
byteBuf.readByte(); // Check sum
}
}
}
}
@@ -37,11 +37,7 @@ public final class VehicleMovePacket implements FallbackPacket {
@Override
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeDouble(x);
byteBuf.writeDouble(y);
byteBuf.writeDouble(z);
byteBuf.writeFloat(yaw);
byteBuf.writeFloat(pitch);
throw new UnsupportedOperationException();
}
@Override
@@ -37,11 +37,11 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
// Disconnect the player if there is no CAPTCHA available at the moment
if (!CaptchaPreparer.isCaptchaAvailable()) {
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
user.disconnect(Sonar.get0().getConfig().getVerification().getCurrentlyPreparing());
throw QuietDecoderException.INSTANCE;
}
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
this.tries = Sonar.get0().getConfig().getVerification().getMap().getMaxTries();
// We may not be on slot 4, so we need to make sure to synchronize the current slot
user.delayedWrite(CAPTCHA_HELD_ITEM_SLOT);
@@ -66,7 +66,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
@Override
public void handle(final @NotNull FallbackPacket packet) {
// Check if the player took too long to enter the CAPTCHA
final int maxDuration = Sonar.get().getConfig().getVerification().getMap().getMaxDuration();
final int maxDuration = Sonar.get0().getConfig().getVerification().getMap().getMaxDuration();
checkState(!user.getLoginTimer().elapsed(maxDuration), "took too long to enter CAPTCHA");
if (packet instanceof SystemChatPacket) {
@@ -83,7 +83,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
} else if (packet instanceof SetPlayerPositionPacket
|| packet instanceof SetPlayerPositionRotationPacket) {
// A position packet is sent approximately every second
if (Sonar.get().getConfig().getVerification().getGamemode().isSurvivalOrAdventure()) {
if (Sonar.get0().getConfig().getVerification().getGamemode().isSurvivalOrAdventure()) {
final long difference = maxDuration - user.getLoginTimer().delay();
final int index = (int) (difference / 1000D);
// Make sure we can safely get and send the packet
@@ -38,14 +38,14 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Bedrock users start falling immediately
this.canFall = user.isGeyser();
// We don't want to check Geyser players for valid gravity, as this might cause issues because of the protocol
this.enableGravityCheck = !user.isGeyser() && Sonar.get().getConfig().getVerification().getGravity().isEnabled();
this.enableCollisionsCheck = !user.isGeyser() && Sonar.get().getConfig().getVerification().getGravity().isCheckCollisions();
this.enableGravityCheck = !user.isGeyser() && Sonar.get0().getConfig().getVerification().getGravity().isEnabled();
this.enableCollisionsCheck = !user.isGeyser() && Sonar.get0().getConfig().getVerification().getGravity().isCheckCollisions();
// First, write the JoinGame packet to the buffer
user.delayedWrite(joinGame);
// Then, write the ClientAbilities packet to the buffer
// This is only necessary if the player is in creative mode
if (Sonar.get().getConfig().getVerification().getGamemode() == CREATIVE) {
if (Sonar.get0().getConfig().getVerification().getGamemode() == CREATIVE) {
user.delayedWrite(DEFAULT_ABILITIES);
}
// Write the DefaultSpawnPosition packet to the buffer
@@ -64,9 +64,13 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
// Make sure the client knows that we're sending chunks next
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_3)) {
user.delayedWrite(START_WRITING_CHUNKS);
for (final FallbackPacket section : EMPTY_CHUNK_DATA) {
user.delayedWrite(section);
}
} else {
user.delayedWrite(EMPTY_CHUNK_DATA[0]);
}
// Teleport player into an empty world by sending an empty chunk packet
user.delayedWrite(EMPTY_CHUNK_DATA);
// Spawn the invisible platform below the player
if (enableCollisionsCheck) {
final int index = RANDOM.nextInt(BLOCKS_PACKETS.length);
@@ -188,8 +192,8 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
this.y = y;
// Log/debug position if enabled in the configuration
if (Sonar.get().getConfig().getVerification().isDebugXYZPositions()) {
Sonar.get().getLogger().info("{}: {}/{}/{} ly={}, dy={}, h={}, g={}, r={}",
if (Sonar.get0().getConfig().getVerification().isDebugXYZPositions()) {
Sonar.get0().getLogger().info("{}: {}/{}/{} ly={}, dy={}, h={}, g={}, r={}",
user.getUsername(), x, y, z, lastY, deltaY, blockHeight, onGround, rotated);
}
@@ -262,7 +266,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
private void failOrShowCaptcha(final String debug) {
// Do not throw an exception if the user configured to display the CAPTCHA instead
if (Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
if (Sonar.get0().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
user.setForceCaptcha(true);
markSuccess();
return;
@@ -80,11 +80,11 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
} 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;
// Disable the check for any further packets the client might send
// while loading the world (or similar).
expectedKeepAliveId = 0;
}
} else if (packet instanceof LoginAcknowledgedPacket) {
// Prevent users from sending multiple LoginAcknowledged packets
checkState(!acknowledgedLogin, "sent duplicate login ack");
@@ -108,15 +108,12 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
}
// Ensure that the client locale is correct
validateClientLocale(clientInformation.getLocale());
// Check if the player sent an unused bit flag in the skin section
// TODO: check if this causes issues with cosmetics in pvp clients
checkState((clientInformation.getSkinParts() & 0x80) == 0,
"sent unused bit flag: " + clientInformation.getSkinParts());
receivedClientInfo = true;
} else if (packet instanceof PluginMessagePacket) {
final PluginMessagePacket pluginMessage = (PluginMessagePacket) packet;
// TODO: Resolve as namespace (?)
final boolean usingModernChannel = pluginMessage.getChannel().equals("minecraft:brand");
final boolean usingLegacyChannel = pluginMessage.getChannel().equals("MC|Brand");
@@ -125,17 +122,11 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
return;
}
// Make sure the player isn't sending the client brand multiple times
checkState(!receivedClientBrand, "sent duplicate plugin message");
// 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().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_13),
"illegal PluginMessage channel: " + pluginMessage.getChannel());
// Validate the client branding using a regex to filter unwanted characters.
if (Sonar.get().getConfig().getVerification().getBrand().isEnabled()) {
if (Sonar.get0().getConfig().getVerification().getBrand().isEnabled()) {
// Make sure the player isn't sending the client brand multiple times
checkState(!receivedClientBrand, "sent duplicate client brand");
validateClientBrand(pluginMessage.getData());
}
@@ -150,15 +141,15 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
user.delayedWrite(packet);
}
// Write the FinishConfiguration packet to the buffer
user.delayedWrite(FINISH_CONFIGURATION);
user.delayedWrite(FinishConfigurationPacket.INSTANCE);
// Send all packets in one flush
user.channel().flush();
}
private void markSuccess() {
if (user.channel().isActive()) {
if (!Sonar.get().getConfig().getVerification().getValidNameRegex().matcher(user.getUsername()).matches()) {
user.disconnect(Sonar.get().getConfig().getVerification().getInvalidUsername());
if (!Sonar.get0().getConfig().getVerification().getValidNameRegex().matcher(user.getUsername()).matches()) {
user.disconnect(Sonar.get0().getConfig().getVerification().getInvalidUsername());
return;
}
@@ -170,7 +161,7 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
void validateClientInformation() {
checkState(receivedClientInfo, "didn't send client settings");
checkState(receivedClientBrand, "didn't send plugin message");
checkState(receivedClientBrand, "didn't send client brand");
}
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
@@ -183,25 +174,23 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
// 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");
// Check if the decoded client brand string is too long
checkState(data.length < Sonar.get().getConfig().getVerification().getBrand().getMaxLength(),
checkState(data.length < Sonar.get0().getConfig().getVerification().getBrand().getMaxLength(),
"client brand contains too much data: " + data.length);
// 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().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8)) {
if (user.getProtocolVersion().greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_8) && brand.length() > 1) {
brand = brand.substring(1);
}
// Check for illegal client brands
checkState(!brand.equals("Vanilla"), "illegal client brand: " + brand);
// Regex pattern for validating client brands
final Pattern pattern = Sonar.get().getConfig().getVerification().getBrand().getValidRegex();
final Pattern pattern = Sonar.get0().getConfig().getVerification().getBrand().getValidRegex();
checkState(pattern.matcher(brand).matches(), "client brand does not match pattern: " + brand);
}
private void validateClientLocale(final @NotNull String locale) {
// Check the client locale by performing a simple regex check
// that disallows non-ascii characters by default.
final Pattern pattern = Sonar.get().getConfig().getVerification().getValidLocaleRegex();
final Pattern pattern = Sonar.get0().getConfig().getVerification().getValidLocaleRegex();
checkState(pattern.matcher(locale).matches(), "client locale does not match pattern: " + locale);
}
}
@@ -170,9 +170,9 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
private void markSuccess() {
// Pass the player to the next best verification handler
if (!user.isGeyser() && Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
if (!user.isGeyser() && Sonar.get0().getConfig().getVerification().getVehicle().isEnabled()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackVehicleHandler(user));
} else if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
} else if (user.isForceCaptcha() || Sonar.get0().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
// The player has passed all checks
@@ -142,7 +142,7 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
private void markSuccess() {
// Pass the player to the next best verification handler
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
if (user.isForceCaptcha() || Sonar.get0().getFallback().shouldPerformCaptcha()) {
user.channel().pipeline().get(FallbackPacketDecoder.class).setListener(new FallbackCaptchaHandler(user));
} else {
finishVerification();
@@ -185,7 +185,7 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
inputs++;
// Check if we've received more than the minimum number of packets
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
final int minimumPackets = Sonar.get0().getConfig().getVerification().getVehicle().getMinimumPackets();
if (inputs > minimumPackets && rotations > minimumPackets
&& paddles > minimumPackets && vehicleMoves > minimumPackets) {
// Move on to the next stage
@@ -49,11 +49,11 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
GlobalSonarStatistics.totalSuccessfulVerifications++;
// Add verified player to the database
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(
Sonar.get0().getVerifiedPlayerController().add(new VerifiedPlayer(
user.getFingerprint(), user.getLoginTimer().getStart()));
// Call the VerifySuccessEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(user));
Sonar.get0().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.
@@ -71,11 +71,11 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
}
} else {
// Disconnect player with the verification success message
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
user.disconnect(Sonar.get0().getConfig().getVerification().getVerificationSuccess());
}
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.successful")
Sonar.get0().getLogger().info(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.logs.successful")
.replace("<username>", user.getUsername())
.replace("<time-taken>", user.getLoginTimer().toString()));
}
@@ -83,46 +83,46 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
protected final void fail(final @NotNull String reason) {
GlobalSonarStatistics.totalFailedVerifications++;
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationFailed());
user.disconnect(Sonar.get0().getConfig().getVerification().getVerificationFailed());
final boolean shouldLog = Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack();
final boolean shouldLog = Sonar.get0().getAttackTracker().getCurrentAttack() == null
|| Sonar.get0().getConfig().getVerification().isLogDuringAttack();
if (shouldLog) {
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.failed")
Sonar.get0().getLogger().info(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.logs.failed")
.replace("<username>", user.getUsername())
.replace("<ip>", Sonar.get().getConfig().formatAddress(user.getInetAddress()))
.replace("<ip>", Sonar.get0().getConfig().formatAddress(user.getInetAddress()))
.replace("<protocol>", user.getProtocolVersion().getName())
.replace("<reason>", reason));
}
// Call the VerifyFailedEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(user, reason));
Sonar.get0().getEventManager().publish(new UserVerifyFailedEvent(user, reason));
// Use a label, so we can easily add more code beneath this method in the future
blacklist: {
final String hostAddress = user.getInetAddress().getHostAddress();
final int score = Sonar.get().getFallback().getBlacklist().get(hostAddress, __ -> 0);
final int score = Sonar.get0().getFallback().getBlacklist().get(hostAddress, __ -> 0);
final int newScore = score + 1;
Sonar.get().getFallback().getBlacklist().put(hostAddress, newScore);
Sonar.get0().getFallback().getBlacklist().put(hostAddress, newScore);
// The user is allowed to disable the blacklist entirely by setting the threshold to 0
final int limit = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
final int limit = Sonar.get0().getConfig().getVerification().getBlacklistThreshold();
// The player hasn't been blacklisted yet, so skip this iteration
if (newScore < limit) break blacklist;
GlobalSonarStatistics.totalBlacklistedPlayers++;
// Call the BotBlacklistedEvent for external API usage
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(user));
Sonar.get0().getEventManager().publish(new UserBlacklistedEvent(user));
if (shouldLog) {
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
Sonar.get0().getLogger().info(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
.replace("<username>", user.getUsername())
.replace("<ip>", Sonar.get().getConfig().formatAddress(user.getInetAddress()))
.replace("<ip>", Sonar.get0().getConfig().formatAddress(user.getInetAddress()))
.replace("<protocol>", user.getProtocolVersion().getName()));
}
}
@@ -20,9 +20,11 @@ package xyz.jonesdev.sonar.common.service;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.update.UpdateChecker;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import java.sql.SQLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -30,6 +32,7 @@ import java.util.concurrent.TimeUnit;
@UtilityClass
public final class ScheduledServiceManager {
private final ScheduledExecutorService VERBOSE = createScheduledExecutor("sonar-verbose-thread");
private final ScheduledExecutorService DB_CLEANUP = createScheduledExecutor("sonar-db-cleanup-thread");
private final ScheduledExecutorService FALLBACK_QUEUE = createScheduledExecutor("sonar-queue-thread");
private final ScheduledExecutorService STATISTICS = createScheduledExecutor("sonar-statistics-thread");
private final ScheduledExecutorService UPDATE_NOTIFIER = createScheduledExecutor("sonar-update-notifier");
@@ -44,30 +47,43 @@ public final class ScheduledServiceManager {
}
public void start() {
if (Sonar.get0().getVerifiedPlayerController().getCachedDatabaseType() != SonarConfiguration.Database.Type.NONE) {
DB_CLEANUP.scheduleAtFixedRate(() -> {
try {
Sonar.get0().getVerifiedPlayerController().clearOld(
Sonar.get0().getVerifiedPlayerController().getMaximumAge(), true);
} catch (SQLException exception) {
Sonar.get0().getLogger().error("Error cleaning database entries!");
exception.printStackTrace(System.err);
}
}, Sonar.get0().getVerifiedPlayerController().getMaximumAge(), 1L, TimeUnit.DAYS);
}
VERBOSE.scheduleAtFixedRate(() -> {
// Make sure to clean up the cached statistics since we don't want to display wrong values
GlobalSonarStatistics.cleanUpCaches();
Sonar.get().getFallback().getBlacklist().cleanUp();
Sonar.get0().getFallback().getBlacklist().cleanUp();
// Update the attack tracker
Sonar.get().getAttackTracker().checkIfUnderAttack();
Sonar.get0().getAttackTracker().checkIfUnderAttack();
// Publish the action bar notifications
Sonar.get().getActionBarNotificationHandler().handleNotification();
Sonar.get0().getActionBarNotificationHandler().handleNotification();
}, 0L, 250L, TimeUnit.MILLISECONDS);
FALLBACK_QUEUE.scheduleAtFixedRate(() -> Sonar.get().getFallback().getQueue().poll(),
FALLBACK_QUEUE.scheduleAtFixedRate(() -> Sonar.get0().getFallback().getQueue().poll(),
1L, 1L, TimeUnit.SECONDS);
STATISTICS.scheduleAtFixedRate(GlobalSonarStatistics::hitEverySecond,
0L, 1L, TimeUnit.SECONDS);
// This config setting only updates when the server is restarted
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("general.check-for-updates")) {
if (Sonar.get0().getConfig().getGeneralConfig().getBoolean("general.check-for-updates")) {
UPDATE_NOTIFIER.scheduleAtFixedRate(UpdateChecker::checkForUpdates,
0L, 2L, TimeUnit.HOURS);
}
}
public void stop() {
DB_CLEANUP.shutdown();
VERBOSE.shutdown();
FALLBACK_QUEUE.shutdown();
STATISTICS.shutdown();
@@ -130,7 +130,7 @@ public final class GlobalSonarStatistics implements SonarStatistics {
@Override
public int getTotalPlayersVerified() {
return Sonar.get().getVerifiedPlayerController().getCache().size();
return Sonar.get0().getVerifiedPlayerController().getCache().size();
}
@Override
@@ -145,7 +145,7 @@ public final class GlobalSonarStatistics implements SonarStatistics {
@Override
public long getCurrentAttemptedVerifications() {
return Sonar.get().getFallback().getConnected().size();
return Sonar.get0().getFallback().getConnected().size();
}
@Override
@@ -155,7 +155,7 @@ public final class GlobalSonarStatistics implements SonarStatistics {
@Override
public long getCurrentBlacklistSize() {
return Sonar.get().getFallback().getBlacklist().estimatedSize();
return Sonar.get0().getFallback().getBlacklist().estimatedSize();
}
@Override
@@ -44,18 +44,18 @@ public final class BlacklistCommand extends Subcommand {
// Make sure the given IP address is valid
if (rawAddress == null) return;
if (Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
if (Sonar.get0().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
return;
}
Sonar.get().getFallback().getBlacklist().put(rawAddress, 1337 /* arbitrarily high number */);
Sonar.get0().getFallback().getBlacklist().put(rawAddress, 1337 /* arbitrarily high number */);
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
break;
}
@@ -70,45 +70,45 @@ public final class BlacklistCommand extends Subcommand {
// Make sure the given IP address is valid
if (rawAddress == null) return;
if (!Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
if (!Sonar.get0().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getFallback().getBlacklist().invalidate(rawAddress);
Sonar.get0().getFallback().getBlacklist().invalidate(rawAddress);
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("ip", rawAddress)));
break;
}
case "clear": {
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
final long blacklistSize = Sonar.get0().getFallback().getBlacklist().estimatedSize();
if (blacklistSize == 0) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.empty"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.empty"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getFallback().getBlacklist().invalidateAll();
Sonar.get0().getFallback().getBlacklist().invalidateAll();
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.clear"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.clear"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
break;
}
case "size": {
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
final long blacklistSize = Sonar.get0().getFallback().getBlacklist().estimatedSize();
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.size"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.blacklist.size"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
break;
}
@@ -49,10 +49,10 @@ public final class DumpCommand extends Subcommand {
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(),
Sonar.get().getPlatform(),
Sonar.get().getVersion().getGitBranch(),
Sonar.get().getVersion().getGitCommit()
Sonar.get0().getVersion().getFormatted(),
Sonar.get0().getPlatform(),
Sonar.get0().getVersion().getGitBranch(),
Sonar.get0().getVersion().getGitCommit()
));
mappings.put("runtime", new Dump.Runtime(
getVirtualCores(),
@@ -72,7 +72,7 @@ public final class DumpCommand extends Subcommand {
formatMemory(getFreeMemory()),
formatMemory(getUsedMemory())
));
Sonar.get().getLogger().info(Sonar.get().getConfig().getMessagesConfig().getString("commands.dump.log")
Sonar.get0().getLogger().info(Sonar.get0().getConfig().getMessagesConfig().getString("commands.dump.log")
.replace("<dumped-json-data>", GSON.toJson(mappings)));
}
@@ -33,17 +33,17 @@ public final class NotifyCommand extends Subcommand {
@Override
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
if (Sonar.get().getChatNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get().getChatNotificationHandler().unsubscribe(source.getUuid());
if (Sonar.get0().getChatNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get0().getChatNotificationHandler().unsubscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.unsubscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.notify.unsubscribe"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getChatNotificationHandler().subscribe(source.getUuid());
Sonar.get0().getChatNotificationHandler().subscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.subscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.notify.subscribe"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
}
}
@@ -34,16 +34,16 @@ public final class ReloadCommand extends Subcommand {
@Override
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())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.reload.start"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
final SystemTimer timer = new SystemTimer();
Sonar.get().reload();
Sonar.get0().reload();
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.reload.finish"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.reload.finish"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("time-taken", String.valueOf(timer.delay()))));
}
}
@@ -47,16 +47,16 @@ public final class StatisticsCommand extends Subcommand {
type = args[1].toLowerCase();
} catch (Exception exception) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.unknown-type"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.statistics.unknown-type"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("statistics", getArguments())));
return;
}
}
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.header"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.statistics.header"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("statistics-type", type)));
source.sendMessage(Component.empty());
@@ -64,29 +64,29 @@ public final class StatisticsCommand extends Subcommand {
switch (type) {
case "general": {
final long seconds = Sonar.get().getLaunchTimer().delay() / 1000L;
final long seconds = Sonar.get0().getLaunchTimer().delay() / 1000L;
final long days = seconds / (24L * 60L * 60L);
final long hours = (seconds % (24L * 60L * 60L)) / (60L * 60L);
final long minutes = (seconds % (60L * 60L)) / 60L;
final String serverUptime = String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L);
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("verified", Sonar.DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().getCache().size())),
Placeholder.unparsed("verifying", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("queued", Sonar.DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("verified", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getVerifiedPlayerController().getCache().size())),
Placeholder.unparsed("verifying", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("queued", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("server-uptime", serverUptime),
Placeholder.unparsed("total-joins", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("total-attempts", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("total-failed", Sonar.DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications()))
Placeholder.unparsed("total-joins", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("total-attempts", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("total-failed", Sonar.DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalFailedVerifications()))
};
break;
}
case "cpu": {
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("process-cpu", Sonar.DECIMAL_FORMAT.format(getProcessCPUUsage())),
Placeholder.unparsed("system-cpu", Sonar.DECIMAL_FORMAT.format(getSystemCPUUsage())),
Placeholder.unparsed("average-process-cpu", Sonar.DECIMAL_FORMAT.format(getAverageProcessCPUUsage())),
@@ -98,7 +98,7 @@ public final class StatisticsCommand extends Subcommand {
case "memory": {
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("max-memory", formatMemory(getMaxMemory())),
@@ -109,17 +109,17 @@ public final class StatisticsCommand extends Subcommand {
case "network": {
placeholders = new TagResolver.Single[]{
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("incoming-traffic", Sonar.get0().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get0().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalOutgoingBandwidth())),
};
break;
}
}
final List<String> parts = Sonar.get().getConfig().getMessagesConfig().getStringList(
final List<String> parts = Sonar.get0().getConfig().getMessagesConfig().getStringList(
"commands.statistics." + type);
for (final String msg : parts) {
source.sendMessage(MiniMessage.miniMessage().deserialize(msg, placeholders));
@@ -34,19 +34,19 @@ public final class VerboseCommand extends Subcommand {
@Override
protected void execute(final @NotNull InvocationSource source, final String @NotNull [] args) {
if (Sonar.get().getActionBarNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get().getActionBarNotificationHandler().unsubscribe(source.getUuid());
if (Sonar.get0().getActionBarNotificationHandler().isSubscribed(source.getUuid())) {
Sonar.get0().getActionBarNotificationHandler().unsubscribe(source.getUuid());
// Reset ActionBar component when unsubscribing
source.getAudience().sendActionBar(Component.empty());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.unsubscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verbose.unsubscribe"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getActionBarNotificationHandler().subscribe(source.getUuid());
Sonar.get0().getActionBarNotificationHandler().subscribe(source.getUuid());
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.subscribe"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verbose.subscribe"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
}
}
@@ -49,17 +49,17 @@ public final class VerifiedCommand extends Subcommand {
final String username = args[3];
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (!Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
if (!Sonar.get0().getVerifiedPlayerController().getCache().contains(fingerprint)) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().remove(fingerprint);
Sonar.get0().getVerifiedPlayerController().remove(fingerprint);
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.remove"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.remove"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("ip", hostAddress),
Placeholder.unparsed("username", username)));
break;
@@ -78,46 +78,46 @@ public final class VerifiedCommand extends Subcommand {
final String username = args[3];
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
if (Sonar.get0().getVerifiedPlayerController().getCache().contains(fingerprint)) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.already"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.already"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(fingerprint, System.currentTimeMillis()));
Sonar.get0().getVerifiedPlayerController().add(new VerifiedPlayer(fingerprint, System.currentTimeMillis()));
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.add"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.add"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("ip", hostAddress),
Placeholder.unparsed("username", username)));
break;
}
case "clear": {
final int verifiedSize = Sonar.get().getVerifiedPlayerController().getCache().size();
final int verifiedSize = Sonar.get0().getVerifiedPlayerController().getCache().size();
if (verifiedSize == 0) {
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.empty"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.empty"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())));
return;
}
Sonar.get().getVerifiedPlayerController().clearAll();
Sonar.get0().getVerifiedPlayerController().clearAll();
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.clear"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.clear"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
break;
}
case "size": {
final int verifiedSize = Sonar.get().getVerifiedPlayerController().getCache().size();
final int verifiedSize = Sonar.get0().getVerifiedPlayerController().getCache().size();
source.sendMessage(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.size"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
Sonar.get0().getConfig().getMessagesConfig().getString("commands.verified.size"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
break;
}
@@ -34,6 +34,8 @@ public class FakeChannelUtil {
public boolean isFakeChannel(final @NotNull Channel channel) {
final String simpleClassName = channel.getClass().getSimpleName();
// Player spoof plugins use fake channels (e.g., Spoof Engine uses "FakeChannel")
return "FakeChannel".equals(simpleClassName) || "SpoofedChannel".equals(simpleClassName);
return "FakeChannel".equals(simpleClassName)
|| "SpoofedChannel".equals(simpleClassName)
|| "EmbeddedChannel".equals(simpleClassName);
}
}
@@ -23,7 +23,7 @@ public final class ReflectiveOperationException extends RuntimeException {
public ReflectiveOperationException(final Throwable throwable) {
super(throwable);
Sonar.get().getLogger().error("A reflective operation resulted in error:");
Sonar.get0().getLogger().error("A reflective operation resulted in error:");
throwable.printStackTrace(System.err);
}
}
@@ -110,6 +110,9 @@ verification:
# Pokud není obrázek nalezen, bude jako výchozí pozadí použit šum
# Zde můžete jednoduše zadat název souboru, pokud je soubor v adresáři pluginu
background: ''
# Styl mapy CAPTCHA (Výchozí: 'modern').
# Chcete-li použít starý design, nastavte tuto hodnotu na 'legacy'.
style: 'modern'
# Kolik odpovědí by měl Sonar předgenerovat?
# Tento úkol běží souběžně v pozadí
precompute: 500
@@ -139,7 +142,7 @@ verification:
# Je-li povoleno, hráč bude přenesen zpět na původní server
# po úspěšném absolvování ověření bota.
# Tuto funkci představil Mojang ve verzi Minecraft 1.20.5
# Tutoriál: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Měl by Sonar přenést hráče na původní server (místo toho, aby je vyhodil)?
# Aby to fungovalo, musíte tuto funkci povolit v konfiguraci vašeho serveru
@@ -184,7 +187,7 @@ verification:
# Měl by Sonar také kontrolovat hráče Geyser (Bedrock)?
# Tato funkce je experimentální a může způsobit problémy
# Pokud je toto zakázáno, hráči Bedrock budou přeskočeni
check-geyser-players: true
check-geyser-players: false
# Doba, za kterou hráč přestal odesílat pakety před odpojením
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
read-timeout: 8000
@@ -107,6 +107,9 @@ verification:
# Wenn das Bild nicht gefunden wird, wird ein Rauschen als Standardhintergrund verwendet
# Sie können hier einfach den Namen der Datei eingeben, wenn sich die Datei im Plugin-Verzeichnis befindet
background: ''
# Stil der Karte CAPTCHA (Standard: 'modern').
# Um das alte Design zu verwenden, setzen Sie dies auf 'legacy'.
style: 'modern'
# Wie viele Antworten soll Sonar vorbereiten?
# Dieser Vorgang erfolgt asynchron im Hintergrund
precompute: 500
@@ -136,7 +139,7 @@ verification:
# Wenn aktiviert, wird der Player zurück zum Ursprungsserver gesendet,
# falls dieser die der Bot-Verifizierung erfolgreich besteht.
# Diese Funktion wurde von Mojang in Minecraft Version 1.20.5 eingeführt
# Anleitung: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Sollte Sonar den Spieler auf den Ursprungsserver senden (anstatt ihn zu kicken)?
# Damit dies funktioniert, müssen Sie die Funktion in der Konfiguration Ihres Servers aktivieren.
@@ -181,7 +184,7 @@ verification:
# Sollte Sonar auch Geyser (Bedrock)-Spieler überprüfen?
# Diese Funktion ist experimentell und kann Probleme verursachen
# Wenn dies deaktiviert ist, werden Bedrock-Spieler übersprungen
check-geyser-players: true
check-geyser-players: false
# Die Zeit, seit der der Spieler keine Pakete mehr sendet, bevor er die Verbindung trennt
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
read-timeout: 8000
@@ -109,6 +109,9 @@ verification:
# If the image isn't found, a noise will be used as the default background.
# You can put the name of the file here if the file is in Sonar's plugin directory
background: ''
# Style of the map CAPTCHA (Default: 'modern').
# To use the old design, set this to 'legacy'.
style: 'modern'
# How many CAPTCHA answers should Sonar prepare?
# This task happens asynchronously in the background
precompute: 500
@@ -139,7 +142,7 @@ verification:
# If enabled, the player will be transferred back to the origin server
# after successfully passing the bot verification.
# This feature was introduced by Mojang in Minecraft version 1.20.5
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Should Sonar transfer the player to the origin server (instead of kicking them)?
# For this to work, you must enable the feature in your server's configuration.
@@ -184,7 +187,7 @@ verification:
# Should Sonar also check Geyser (Bedrock) players?
# This feature is experimental and might cause issues
# If this is disabled, Bedrock players will be skipped
check-geyser-players: true
check-geyser-players: false
# The time since the player stopped sending packets before disconnecting
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
read-timeout: 8000
@@ -109,6 +109,9 @@ verification:
# Si l'image n'est pas trouvée, un bruit sera utilisé comme arrière-plan par défaut
# Vous pouvez simplement mettre le nom du fichier ici si le fichier est dans le répertoire du plugin
background: ''
# Style de la carte CAPTCHA (par défaut : 'moderne').
# Pour utiliser l'ancien design, mettez ce paramètre à 'legacy'.
style: 'modern'
# Combien de réponses Sonar devrait-il préparer ?
# Cette tâche se déroule de manière asynchrone en arrière-plan
precompute: 500
@@ -138,7 +141,7 @@ verification:
# S'il est activé, le joueur sera renvoyé au serveur d'origine
# après avoir passé avec succès la vérification du bot.
# Cette fonctionnalité a été introduite par Mojang dans la version Minecraft 1.20.5
# Tutoriel: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Sonar devrait-il transférer le joueur au serveur d'origine (au lieu de l'expulser) ?
# Pour que cela fonctionne, vous devez activer la fonctionnalité dans la configuration de votre serveur
@@ -183,7 +186,7 @@ verification:
# Sonar devrait-il également vérifier les joueurs Geyser (Bedrock) ?
# Cette fonctionnalité est expérimentale et peut causer des problèmes
# Si cela est désactivé, les joueurs Bedrock seront ignorés
check-geyser-players: true
check-geyser-players: false
# Temps écoulé depuis que le joueur a cessé d'envoyer des paquets avant de se déconnecter
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
read-timeout: 8000
+17 -14
View File
@@ -85,16 +85,16 @@ verification:
# ტიკების მაქსიმალური რაოდენობა რომელითაც მოთამაშე უნდა დაეცეს, რათა პლატფორმაზე დაცემა შეეძლოს. (20 ტიკი = 1 წამი)
max-movement-ticks: 8
# Checks if the player is colliding with blocks spawned below them
# ამოწმებს, ეჯახება თუ არა მოთამაშე მათ ქვემოთ წარმოქმნილ ბლოკებს
collision:
# უნდა შეამოწმოს თუ არა Sonar-მა, ეტაკება თუ არა მოთამაშე ბლოკებს? (რეკომენდებულია)
enabled: true
# ამოწმებს, აგზავნის თუ არა კლიენტი შესაბამის პაკეტებს სატრანსპორტო საშუალებაზე დაჯდომის დროს
vehicle:
# Should Sonar check if the player properly enters a boat?
# უნდა შეამოწმოს თუ არა სონარმა მოთამაშე სწორად შესული თუ არა ნავში?
enabled: true
# Minimum number of packets sent by the client
# კლიენტის მიერ გაგზავნილი პაკეტების მინიმალური რაოდენობა
minimum-packets: 2
# დააწერინეთ მოთამაშეს კოდი ჩატში, რომელიც იქნება ნაჩვენები ვირტუალურ მაპზე, რომელიც ხელში ეჭირებათ
@@ -109,6 +109,9 @@ verification:
# თუ გამოსახულება არ მოიძებნა, ფონად გამოყენებული იქნება noise ი
# შეგიძლიათ უბრალოდ ჩაწეროთ ფაილის სახელი, თუ ის პლაგინის დირექტორიაშია
background: ''
# რუკის სტილი CAPTCHA (ნაგულისხმევი: 'modern').
# ძველი დიზაინის გამოსაყენებლად დააყენეთ ეს 'legacy'.
style: 'modern'
# რამდენი პასუხი უნდა მოამზადოს Sonar-მა?
# ეს ხდება ასინქრონულად (ანუ ამის გამოთვლას სერვერი არ დაელოდება) უკანა ფონზე
precompute: 500
@@ -137,15 +140,15 @@ verification:
# თუ ჩართულია, მოთამაშე გადავა საწყის სერვერში ვერიფიკაციის წარმატებით გავლის შემდეგ
# ეს ფუნქცია დაემატა Mojang-ის მიერ Minecraft-ის 1.20.5 ვერსიაში
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# უნდა გადაიყვანოს თუ არა Sonar-მა მოთამაშე საწყის სერვერზე (მათი გაგდების ნაცვლად)?
# ამისთვის, თქვენ უნდა ჩართოთ ეს ფუნქცია თქვენი სერვერის კონფიგურაციაში
# დამატებით, შეიძლება გინდოდეთ შეამციროთ შესვლის სიხშირის ლიმიტი
# რომელსაც ახორციელებს Velocity ან სხვა პროქსიები/პლაგინები, რადგან ეს შეიძლება ხელს უშლიდეს
# მოთამაშის სწორად გადაყვანას
# If your server allows Bedrock players to join via Geyser, make sure to set up
# TransferTool for Geyser, so Bedrock players are redirected to the correct server.
# თუ თქვენი სერვერი საშუალებას აძლევს Bedrock მოთამაშეებს შეუერთდნენ Geyser-ის საშუალებით, დარწმუნდით, რომ დააყენეთ
# TransferTool Geyser-ისთვის, ასე რომ Bedrock მოთამაშეები გადამისამართდებიან სწორ სერვერზე.
# https://github.com/onebeastchris/TransferTool
enabled: false
# რომელ სერვერზე უნდა გადაიყვანოს Sonar-მა მოთამაშე ვერიფიკაციის გავლის შემდეგ?
@@ -162,15 +165,15 @@ verification:
# - CREATIVE: სიცოცხლის და საჭმლის მაჩვენებლები იშლება
# - ADVENTURE: ყველა UI კომპონენტი ჩანს
gamemode: ADVENTURE
# Daylight time in the verification world
# You can find all daylight times here:
# დღის განათება გადამოწმების სამყაროში
# აქ შეგიძლიათ იპოვოთ მთელი დღის დრო:
# https://minecraft.wiki/w/Daylight_cycle
# This is only a visual setting and does not change the verification process!
# If this value is set to 1000, the packet will not be sent.
# ეს მხოლოდ ვიზუალური პარამეტრია და არ ცვლის გადამოწმების პროცესს!
# თუ ეს მნიშვნელობა დაყენებულია 1000-ზე, პაკეტი არ გაიგზავნება.
time-of-day: 1000
# Sonar caches most packets it sends to the client, including the LoginSuccess packet.
# Which username should Sonar use for the LoginSuccess packet?
# The username will not be visible to the player without client modifications
# Sonar ინახავს პაკეტების უმეტესობას, რომლებსაც ის უგზავნის კლიენტს, მათ შორის LoginSuccess პაკეტს.
# რომელი მომხმარებლის სახელი უნდა გამოიყენოს Sonar-მა LoginSuccess პაკეტისთვის?
# მომხმარებლის სახელი არ იქნება ხილული მოთამაშისთვის კლიენტის ცვლილებების გარეშე
cached-username: "Sonar"
# ჩაიწეროს თუ არა Sonar-მა ახალი ვერიფიკაციის მცდელობები?
log-connections: true
@@ -182,7 +185,7 @@ verification:
# უნდა შეამოწმოს თუ არა Sonar-მა Geyser (Bedrock) მოთამაშეები?
# ეს ფუნქცია ექსპერიმენტულია და შეიძლება გამოიწვიოს პრობლემები
# თუ ეს გამორთულია, Bedrock მოთამაშეები გამოვლენილი იქნება
check-geyser-players: true
check-geyser-players: false
# დროის ოდენობა, რომელიც უნდა გავიდეს, სერვერთან კავშირის გარეშე, სანამ მოთამაშე დროებით გავარდება სერვერიდან.
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
read-timeout: 8000
@@ -109,6 +109,9 @@ verification:
# Als de afbeelding niet wordt gevonden, wordt er een ruis gebruikt als standaardachtergrond
# U kunt eenvoudig de naam van het bestand hier plaatsen als het bestand zich in de plugin map bevindt
background: ''
# Stijl van de CAPTCHA kaart (Standaard: 'modern').
# Om het oude ontwerp te gebruiken, stel je dit in op 'legacy'.
style: 'modern'
# Hoeveel antwoorden moet Sonar voorbereiden?
# Deze taak gebeurt asynchroon op de achtergrond
precompute: 500
@@ -138,7 +141,7 @@ verification:
# Indien ingeschakeld, wordt de speler teruggestuurd naar de oorspronkelijke server
# nadat u de bot-verificatie met succes heeft doorstaan.
# Deze functie is geïntroduceerd door Mojang in Minecraft versie 1.20.5
# Handleiding: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Moet Sonar de speler overbrengen naar de oorspronkelijke server (in plaats van hem te schoppen)?
# Om dit te laten werken, moet u de functie inschakelen in de configuratie van uw server
@@ -183,7 +186,7 @@ verification:
# Moet Sonar ook Geyser (Bedrock) spelers controleren?
# Deze functie is experimenteel en kan problemen veroorzaken
# Als dit is uitgeschakeld, worden Bedrock-spelers overgeslagen
check-geyser-players: true
check-geyser-players: false
# De hoeveelheid tijd die moet verstrijken voordat een speler een time-out krijgt
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
read-timeout: 8000
@@ -109,6 +109,9 @@ verification:
# Jeśli obraz nie zostanie znaleziony, jako domyślne tło zostanie użyty szum
# Możesz umieścić nazwę pliku tutaj, jeśli plik znajduje się w katalogu pluginu
background: ''
# Styl mapy CAPTCHA (domyślnie: 'nowoczesny').
# Aby użyć starego projektu, ustaw to na 'legacy'.
style: 'modern'
# Ile możliwych odpowiedzi powinien przygotować Sonar?
# Zadanie to wykonywane jest asynchronicznie w tle
precompute: 500
@@ -138,7 +141,7 @@ verification:
# Jeśli opcja ta jest włączona, gracz zostanie przeniesiony z powrotem
# na serwer początkowy po pomyślnym przejściu weryfikacji antybotowej.
# Funkcja ta została wprowadzona przez Mojang w wersji 1.20.5
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Czy Sonar powinien przenieść gracza na serwer początkowy (zamiast go wyrzucać)?
# Aby to zadziałało, musisz włączyć tę funkcję w konfiguracji serwera.
@@ -183,7 +186,7 @@ verification:
# Czy Sonar powinien również sprawdzać graczy Geyser (Bedrock)?
# Ta funkcja jest eksperymentalna i może powodować problemy
# Jeśli ta opcja jest wyłączona, gracze Bedrock będą pomijani
check-geyser-players: true
check-geyser-players: false
# Czas, jaki ma upłynąć od zatrzymania wysyłania pakietów przez gracza do wyrzucenia.
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
read-timeout: 8000
@@ -0,0 +1,212 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Precisa de ajuda ou tem dúvidas? https://jonesdev.xyz/discord
# Código-fonte: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development e Sonar Contributors, licenciado sob GPL-3.0
#
# Configurações gerais para o Sonar
general:
# O Sonar deve verificar automaticamente se há novas atualizações?
# O Sonar usa a API do GitHub para verificar se a versão atual está desatualizada
check-for-updates: true
# O Sonar deve registrar os endereços IP dos jogadores no console?
log-player-addresses: true
# Número máximo de jogadores online com o mesmo endereço IP
max-online-per-ip: 3
# Configurações de sensibilidade para o rastreador de ataques
# Edite isso apenas se você souber o que está fazendo
attack-tracker:
# Número mínimo de novos jogadores para que um ataque seja detectado
min-players-for-attack: 8
# Tempo que deve passar para que um ataque seja considerado encerrado
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
min-attack-duration: 30000
# Número de vezes que um incidente deve ser relatado para ser reconhecido como um ataque
# Este número atua como um buffer para filtrar notificações de ataque falsas
min-attack-threshold: 2
# Tempo que deve passar para que um novo ataque seja detectado
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
attack-cooldown-delay: 3000
# Você pode conectar o Sonar a um banco de dados para manter jogadores verificados mesmo após reiniciar seu servidor
# Nota: Endereços IP são salvos em texto simples. Você é responsável por manter seu banco de dados seguro!
database:
# Tipo de banco de dados que o Sonar usa para armazenar jogadores verificados
# Tipos possíveis: NONE, MYSQL, MARIADB, H2
type: NONE
# Arquivo no diretório de plugins do Sonar usado para o banco de dados H2
filename: verified_players
# Endereço do host para autenticação com o banco de dados SQL
host: localhost
# Porta para autenticação com o banco de dados SQL
port: 3306
# Nome do banco de dados SQL
name: sonar
# Nome de usuário para autenticação com o banco de dados SQL
username: ''
# Senha para autenticação com o banco de dados SQL
password: ''
# Quantos dias o Sonar deve manter jogadores verificados no banco de dados?
maximum-age: 5
# Cada nova solicitação de login será enfileirada para evitar ataques de spam
# A fila é atualizada a cada 1 segundo (20 ticks)
queue:
# Número de polls de fila concorrentes por 1 segundo
max-polls: 30
# Cada novo jogador que se junta pela primeira vez será enviado para
# um servidor limbo leve onde verificações avançadas de bot são realizadas
verification:
# Quando o Sonar deve verificar novos jogadores?
# Tipos possíveis: ALWAYS, DURING_ATTACK
# - ALWAYS: Novos jogadores sempre serão verificados (Recomendado)
# - DURING_ATTACK: Novos jogadores serão verificados apenas durante um ataque
timing: ALWAYS
checks:
# Verifica se o movimento de queda dos jogadores segue a fórmula de gravidade do Minecraft
# Todos os movimentos previstos são pré-calculados para economizar desempenho
gravity:
# O Sonar deve verificar a gravidade do cliente? (Recomendado)
enabled: true
# O Sonar deve fazer o jogador resolver um CAPTCHA se falhar na verificação de gravidade?
captcha-on-fail: false
# O número de ticks que o jogador deve cair para ser permitido atingir a plataforma
max-movement-ticks: 8
# Verifica se o jogador está colidindo com blocos gerados abaixo dele
collision:
# O Sonar deve verificar se o jogador colide com blocos? (Recomendado)
enabled: true
# Verifica se o cliente envia pacotes adequados ao entrar em um barco
vehicle:
# O Sonar deve verificar se o jogador entra corretamente em um barco?
enabled: true
# Número mínimo de pacotes enviados pelo cliente
minimum-packets: 2
# Faz o jogador digitar um código de um mapa virtual no chat
map-captcha:
# Quando o Sonar deve fazer o jogador resolver um CAPTCHA?
# Tipos possíveis: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Novos jogadores sempre receberão um CAPTCHA
# - DURING_ATTACK: Novos jogadores receberão um CAPTCHA apenas durante um ataque
# - NEVER: Novos jogadores nunca receberão um CAPTCHA (Recomendado)
timing: NEVER
# Caminho para a imagem de fundo que o Sonar usa para o CAPTCHA
# Se a imagem não for encontrada, um ruído será usado como fundo padrão.
# Você pode colocar o nome do arquivo aqui se o arquivo estiver no diretório de plugins do Sonar
background: ''
# Estilo do CAPTCHA do mapa (Padrão: 'modern').
# Para usar o design antigo, defina-o como 'legacy'.
style: 'modern'
# Quantas respostas de CAPTCHA o Sonar deve preparar?
# Esta tarefa acontece de forma assíncrona em segundo plano
precompute: 500
# Quanto tempo o Sonar deve esperar até que o jogador falhe no CAPTCHA?
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
max-duration: 30000
# Quantas vezes um jogador deve falhar no CAPTCHA antes de falhar na verificação?
max-tries: 3
# Caracteres (letras/números) que são permitidos na resposta ao CAPTCHA
# Não é recomendável adicionar números ou letras que possam parecer semelhantes.
# Você deve editar isso apenas se souber o que está fazendo
alphabet: 'abcdefhjkmnoprstuxyz'
# Verifica se os jogadores estão enviando uma marca de cliente válida para o servidor
client-brand:
# O Sonar deve verificar marcas de cliente válidas? (Recomendado)
enabled: true
# Regex para validar marcas de cliente durante a verificação
valid-regex: ^[!-~ ]+$
# Comprimento máximo da marca de cliente durante a verificação
max-length: 64
# Regex para validar nomes de usuário durante a verificação
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex para validar o local do cliente durante a verificação
valid-locale-regex: ^[a-zA-Z_]+$
# Se habilitado, o jogador será transferido de volta para o servidor de origem
# após passar com sucesso pela verificação do bot.
# Este recurso foi introduzido pela Mojang na versão 1.20.5 do Minecraft
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# O Sonar deve transferir o jogador para o servidor de origem (em vez de expulsá-lo)?
# Para que isso funcione, você deve habilitar o recurso na configuração do seu servidor.
# Além disso, você pode querer reduzir a quantidade de limitação de taxa de login
# realizada pelo Velocity ou outros proxies/plugins, pois isso pode impedir que o
# jogador seja transferido corretamente.
# Se seu servidor permitir que jogadores Bedrock se juntem via Geyser, certifique-se de configurar
# o TransferTool para Geyser, para que jogadores Bedrock sejam redirecionados para o servidor correto.
# https://github.com/onebeastchris/TransferTool
enabled: false
# Para qual servidor o Sonar deve transferir o jogador quando a verificação for passada?
# Por favor, insira o IP do servidor usado pelos jogadores para se conectar normalmente ao seu servidor.
# Por exemplo, você pode colocar "mc.hypixel.net" ou um IP direto como "1.1.1.1"
destination-host: "play.my-server.com"
# Qual porta o Sonar deve usar ao transferir o jogador para o servidor de origem?
# Se seu servidor não precisar de uma porta para se conectar, você pode deixar isso como 25565.
destination-port: 25565
# O modo de jogo do jogador durante a verificação
# Tipos possíveis: SURVIVAL, CREATIVE, ADVENTURE
# - SURVIVAL: todos os componentes da UI são visíveis
# - CREATIVE: saúde e fome estão ocultas
# - ADVENTURE: todos os componentes da UI são visíveis
gamemode: ADVENTURE
# Tempo de luz do dia no mundo de verificação
# Você pode encontrar todos os tempos de luz do dia aqui:
# https://minecraft.wiki/w/Daylight_cycle
# Esta é apenas uma configuração visual e não altera o processo de verificação!
# Se este valor for definido como 1000, o pacote não será enviado.
time-of-day: 1000
# O Sonar armazena em cache a maioria dos pacotes que envia ao cliente, incluindo o pacote LoginSuccess.
# Qual nome de usuário o Sonar deve usar para o pacote LoginSuccess?
# O nome de usuário não será visível para o jogador sem modificações no cliente
cached-username: "Sonar"
# O Sonar deve registrar novas tentativas de verificação?
log-connections: true
# O Sonar deve registrar novas tentativas de verificação durante ataques?
log-during-attack: false
# O Sonar deve registrar cada movimento/mudança de posição durante a verificação?
# Isso não é recomendado para servidores de produção, mas pode ser útil para detectar erros.
debug-xyz-positions: false
# O Sonar também deve verificar jogadores Geyser (Bedrock)?
# Este recurso é experimental e pode causar problemas
# Se isso estiver desativado, jogadores Bedrock serão ignorados
check-geyser-players: false
# O tempo desde que o jogador parou de enviar pacotes antes de desconectar
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
read-timeout: 8000
# O tempo desde que o servidor parou de enviar pacotes antes de desconectar o jogador
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
write-timeout: 10000
# Quanto tempo um jogador deve esperar antes de se reconectar durante a verificação
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
rejoin-delay: 5000
# Quanto tempo o Sonar deve lembrar o número de falhas de verificação para um jogador?
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
remember-time: 120000
# Quanto tempo um endereço IP deve ser negado de fazer login ao falhar na verificação muitas vezes?
# (Este valor representa o tempo em milissegundos: 1 segundo = 1000 milissegundos)
blacklist-time: 600000
# Com que frequência um jogador deve falhar na verificação para ser colocado na lista negra?
# (Defina este valor como 0 para desativar completamente a lista negra)
blacklist-threshold: 2
# Lista de IDs de protocolo que não podem se juntar ao servidor
# Você pode encontrar a lista completa de todos os IDs de protocolo aqui:
# https://wiki.vg/Protocol_version_numbers
# Por exemplo, Minecraft 1.20 tem o ID 763.
blacklisted-protocols: []
@@ -108,6 +108,9 @@ verification:
# Путь к фоновому изображению, которое использует Sonar для CAPTCHA
# Если изображение не найдено, будет использован шум как фон по умолчанию
background: ''
# Стиль капч (по умолчанию: 'modern').
# Чтобы использовать старый дизайн, установите значение 'legacy'.
style: 'modern'
# Сколько капч должен подготовить Sonar?
# Эта задача выполняется асинхронно в фоновом режиме
precompute: 500
@@ -137,7 +140,7 @@ verification:
# Если включено, игрок будет возвращен на исходный сервер
# после успешного прохождения проверки на бота.
# Эта функция была введена Mojang в версии Minecraft 1.20.5
# Учебник: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Должен ли Sonar переместить игрока на исходный сервер (вместо выкидывания его)?
# Для этого необходимо включить функцию в конфигурации вашего сервера
@@ -182,7 +185,7 @@ verification:
# Следует ли Sonar также проверять игроков Geyser (Bedrock)?
# Эта функция экспериментальна и может вызывать проблемы
# Если это отключено, игроки Bedrock будут пропущены
check-geyser-players: true
check-geyser-players: false
# Время, прошедшее с момента прекращения отправки пакетов игроком до отключения.
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
read-timeout: 8000
@@ -108,6 +108,9 @@ verification:
# 如果未找到指定的图片, 则自动通过噪音生成一个随机的背景图片.
# 如果图片在该插件的配置文件夹下, 则可以简单地输入图片的文件名.
background: ''
# 地图验证码的风格(默认:'modern').
# 要使用旧设计, 请将其设置为 'legacy'.
style: 'modern'
# Sonar 应该一次准备多少个验证码?
precompute: 500
# Sonar 应该给玩家多少时间完成验证码?
@@ -135,7 +138,7 @@ verification:
# 开启后 玩家将在验证成功后被重定向到指定的服务器
# 该功能由 Mojang 在 Minecraft 1.20.5 版本中引入
# 教程: https://youtu.be/aSWrzQVjGnc?t=71
# https://docs.jonesdev.xyz/administration/configuring-sonar/configuring-the-verification/using-1.20.5+-transfers
transfer:
# Sonar 是否应该将玩家重定向到指定的服务器而不是踢出玩家?
# 为此您必须在服务器软件的配置文件中允许接受来自transfer的连接.
@@ -179,7 +182,7 @@ verification:
# Sonar 是否应该检查来自 Geyser 的(基岩版)玩家?
# 此功能是实验性的. 请报告任何因为误判而导致无法通过检查的问题
# 如果关闭该选项 Sonar 将不检查这些玩家以便直接连接到服务器.
check-geyser-players: true
check-geyser-players: false
# 播放器断开连接前停止发送数据包的时间
# (该值为毫秒. 1秒等于1000毫秒.)
read-timeout: 8000
@@ -21,10 +21,9 @@
# If you still want all configuration files to be translated,
# you have to reset the configuration, messages, and webhook settings.
#
# You can view a full list of valid language codes here:
# https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
# You can view a full list of existing translations here:
# https://docs.jonesdev.xyz/sonar/translations
# If a translation does not exist yet, Sonar will use English (en)
#
# If you set this option to "system", Sonar will use the system language.
# Please keep this file unchanged if you don't know what you're doing.
language=system
@@ -0,0 +1,299 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Precisa de ajuda ou tem dúvidas? https://jonesdev.xyz/discord
# Código-fonte: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development e Sonar Contributors, licenciado sob GPL-3.0
#
# Placeholder para cada '<prefix>' neste arquivo de configuração
prefix: <b><yellow>Sonar</b> <gray>» <white>
# Placeholder para cada '<support-url>' neste arquivo de configuração
support-url: https://jonesdev.xyz/discord/
# Placeholder para cada '<header>' neste arquivo de configuração
header:
- <b><yellow>Sonar</b><reset>
- ''
# Placeholder para cada '<footer>' neste arquivo de configuração
footer:
- <gray>Se você acredita que isso é um erro, entre em contato com um administrador.
# Mensagem de desconexão que é mostrada quando alguém se junta, mas há muitos jogadores online com seu endereço IP
too-many-online-per-ip:
- '<header>'
- <red>Há muitos jogadores online com seu endereço IP.
- '<footer>'
# Traduções para comandos
commands:
# Mensagem mostrada quando um jogador tenta executar /sonar sem permissão
no-permission: '<prefix><red>Você não tem permissão para executar este comando.'
# Mensagem mostrada quando alguém usa um comando incorretamente
incorrect-usage: '<prefix><red>Uso: /sonar <subcommand-usage>'
# Mensagem mostrada quando alguém fornece um endereço IP inválido (Formato inválido)
invalid-ip-address: '<prefix>O endereço IP que você forneceu parece ser inválido.'
# Mensagem mostrada quando o console executa um comando que é apenas para jogadores
player-only: '<prefix><red>Você só pode executar este comando como um jogador.'
# Mensagem mostrada quando um jogador executa um comando que é apenas para console
console-only: '<prefix><red>Por razões de segurança, você só pode executar este comando através do console.'
# Mensagem mostrada quando um jogador não tem permissão para executar um determinado subcomando
subcommand-no-permission: '<prefix><red>Você não tem permissão para executar este subcomando. <gray>(<permission>)'
# Traduções para '/sonar'
main:
# Mensagem informativa que é mostrada acima de tudo ao executar o comando principal
header:
- <yellow>Executando <b>Sonar</b> <version> em <platform>.
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development e Contribuidores do Sonar</click>
- ''
- <gray>Precisa de ajuda ou tem alguma dúvida?
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Clique para abrir o Discord)'>Abra um ticket no Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Clique para abrir o GitHub)'>ou abra um novo problema no GitHub.
- ''
# Formatação da lista de subcomandos mostrada ao executar o comando principal
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Somente jogadores: </gray><only-players><br><gray>Requer console: </gray><only-console><br><gray>Permissão: </gray><white><permission><br><gray>Aliases: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
# Formatação para valores de sim (marca de seleção) e não (cruz) no texto de hover dos subcomandos
tick: '<green>✔</green>'
cross: '<red>✗</red>'
# Formatação para as informações da versão
outdated-version: ' <red><click:open_url:''https://github.com/jonesdevelopment/sonar/releases/latest''><hover:show_text:''(Clique para baixar a versão mais recente do Sonar)''>(Desatualizado)</hover></click></red>'
unstable-version: ' <gold>(Instável)</gold>'
api-error: ' <dark_red><hover:show_text:''Verifique os logs do servidor para mais informações''>(Erro de API)</hover></dark_red>'
# Traduções para '/sonar reload'
reload:
# Descrição para este subcomando
description: "Recarregar todas as configurações"
# Mensagem mostrada quando alguém começa a recarregar o Sonar
start: '<prefix>Recarregando o Sonar...'
# Mensagem mostrada quando o Sonar termina de recarregar
finish: '<prefix><green>Recarregado com sucesso <gray>(<time-taken>ms)'
# Traduções para '/sonar verbose'
verbose:
# Descrição para este subcomando
description: "Ativar ou desativar o verbose do Sonar"
# Mensagem mostrada quando um jogador se inscreve no verbose do Sonar
subscribe: '<prefix>Você agora está visualizando o verbose do Sonar.'
# Mensagem mostrada quando um jogador se desinscreve do verbose do Sonar
unsubscribe: '<prefix>Você não está mais visualizando o verbose do Sonar.'
# Traduções para '/sonar notify'
notify:
# Descrição para este subcomando
description: "Ativar ou desativar notificações de ataque"
# Mensagem mostrada quando um jogador se inscreve nas notificações de ataque do Sonar
subscribe: '<prefix>Você agora está visualizando as notificações de ataque do Sonar.'
# Mensagem mostrada quando um jogador se desinscreve das notificações de ataque do Sonar
unsubscribe: '<prefix>Você não está mais visualizando as notificações de ataque do Sonar.'
# Traduções para '/sonar blacklist'
blacklist:
# Descrição para este subcomando
description: "Gerenciar endereços IP na lista negra"
# Mensagem mostrada quando alguém tenta limpar a lista negra, mas está vazia
empty: '<prefix>A lista negra está atualmente vazia. Portanto, nenhum endereço IP foi removido da lista negra.'
# Mensagem mostrada quando alguém limpa a lista negra
clear: '<prefix>Você removeu com sucesso um total de <amount> endereço(s) IP da lista negra.'
# Mensagem mostrada quando alguém verifica o tamanho da lista negra
size: '<prefix>A lista negra atualmente contém <amount> endereço(s) IP.'
# Mensagem mostrada quando alguém remove um endereço IP da lista negra
remove: '<prefix>Removido com sucesso <ip> da lista negra.'
# Mensagem mostrada quando alguém adiciona um endereço IP à lista negra
add: '<prefix>Adicionado com sucesso <ip> à lista negra.'
# Mensagem mostrada quando alguém tenta adicionar um endereço IP à lista negra, mas já está na lista
ip-duplicate: '<prefix>O endereço IP que você forneceu já está na lista negra.'
# Mensagem mostrada quando alguém remove um endereço IP da lista negra, mas não está na lista
ip-not-found: '<prefix>O endereço IP que você forneceu não está na lista negra.'
# Traduções para '/sonar verified'
verified:
# Descrição para este subcomando
description: "Gerenciar endereços IP verificados"
# Mensagem mostrada quando alguém tenta limpar a lista de jogadores verificados, mas está vazia
empty: '<prefix>A lista de jogadores verificados está atualmente vazia. Portanto, nenhum jogador foi desverificado.'
# Mensagem mostrada quando alguém limpa a lista de jogadores verificados
clear: '<prefix>Você desverificou com sucesso um total de <amount> jogador(es) únicos.'
# Mensagem mostrada quando alguém verifica o tamanho da lista de jogadores verificados
size: '<prefix>Atualmente há <amount> jogador(es) únicos verificados.'
# Mensagem mostrada quando alguém desverifica um endereço IP
remove: '<prefix>Desverificado com sucesso <ip>.'
# Mensagem mostrada quando alguém verifica um endereço IP
add: '<prefix>Verificado com sucesso <ip>.'
# Mensagem mostrada quando alguém tenta verificar um endereço IP/UUID que já está verificado
already: '<prefix>O endereço IP e UUID que você forneceu já estão verificados.'
# Mensagem mostrada quando alguém desverifica um endereço IP, mas não está verificado
ip-not-found: '<prefix>O endereço IP que você forneceu não está verificado.'
# Mensagem mostrada quando alguém consulta o histórico de verificação de um endereço IP
history: '<prefix><yellow>UUIDs anteriores para <ip>:'
history-entry: ' <gray>▪ <white><uuid>'
# Traduções para '/sonar statistics'
statistics:
# Descrição para este subcomando
description: "Mostrar estatísticas da sessão deste servidor"
# Mensagem informativa que é mostrada acima de tudo ao visualizar as estatísticas
header: '<prefix>Mostrando estatísticas de <statistics-type> para esta sessão:'
# Mensagem mostrada quando um jogador tenta visualizar um tipo de estatística desconhecido
unknown-type: '<prefix><red>Tipo de estatística desconhecido! Estatísticas disponíveis: <gray><statistics>'
# Formato da mensagem de estatísticas gerais
general:
- ' <dark_aqua>▪ <gray>Endereços IP verificados: <white><verified>'
- ' <dark_aqua>▪ <gray>Endereços IP em verificação: <white><verifying>'
- ' <dark_aqua>▪ <gray>Endereços IP na lista negra: <white><blacklisted>'
- ' <dark_aqua>▪ <gray>Logins atualmente na fila: <white><queued>'
- ' <dark_aqua>▪ <gray>Total de entradas não únicas: <white><total-joins>'
- ' <dark_aqua>▪ <gray>Total de tentativas de verificação: <white><total-attempts>'
- ' <dark_aqua>▪ <gray>Total de falhas de verificação: <white><total-failed>'
- ' <dark_aqua>▪ <gray>Tempo de atividade do servidor: <white><server-uptime>'
# Formato da mensagem de estatísticas de CPU
cpu:
- ' <dark_aqua>▪ <gray>Uso de CPU do processo agora: <white><process-cpu>%'
- ' <dark_aqua>▪ <gray>Uso de CPU do sistema agora: <white><system-cpu>%'
- ' <dark_aqua>▪ <gray>Uso de CPU do processo por núcleo: <white><average-process-cpu>%'
- ' <dark_aqua>▪ <gray>Uso de CPU do sistema por núcleo: <white><average-system-cpu>%'
- ' <dark_aqua>▪ <gray>Total de CPUs virtuais: <white><virtual-core-count>'
# Formato da mensagem de estatísticas de memória
memory:
- ' <dark_aqua>▪ <gray>Total de memória livre: <white><free-memory>'
- ' <dark_aqua>▪ <gray>Total de memória usada: <white><used-memory>'
- ' <dark_aqua>▪ <gray>Total de memória máxima: <white><max-memory>'
- ' <dark_aqua>▪ <gray>Total de memória alocada: <white><total-memory>'
# Formato da mensagem de estatísticas de rede
network:
- ' <dark_aqua>▪ <gray>Largura de banda usada atualmente (entrada): <white><incoming-traffic>'
- ' <dark_aqua>▪ <gray>Largura de banda usada atualmente (saída): <white><outgoing-traffic>'
- ' <dark_aqua>▪ <gray>Largura de banda total usada (entrada): <white><incoming-traffic-ttl>'
- ' <dark_aqua>▪ <gray>Largura de banda total usada (saída): <white><incoming-traffic-ttl>'
# Traduções para '/sonar dump'
dump:
# Descrição para este subcomando
description: "Imprimir informações do desenvolvedor"
# Formato das informações enviadas ao console
log: "Dump gerado: <dumped-json-data>"
# Traduções para todas as mensagens durante o processo de verificação
verification:
# Traduções para todas as mensagens de depuração durante a verificação
logs:
# Mensagem registrada no console sempre que um novo jogador se junta ao servidor
connection: '<username>/<ip> (<protocol>) se conectou.'
# Mensagem registrada no console sempre que um jogador falha na verificação
failed: '<username>/<ip> (<protocol>) falhou no teste do bot por: <reason>'
# Mensagem registrada no console sempre que um jogador é colocado na lista negra
blacklisted: '<username>/<ip> (<protocol>) foi colocado na lista negra por muitas tentativas falhadas'
# Mensagem registrada no console sempre que um jogador é verificado
successful: '<username> foi verificado com sucesso (<time-taken>s!).'
# Mensagens que são mostradas ao jogador quando ele deve inserir o CAPTCHA
captcha:
# Mensagem mostrada ao jogador quando ele deve inserir a resposta ao CAPTCHA
enter: '<prefix><green>Por favor, insira o texto no chat que é exibido no mapa.'
# Mensagem mostrada ao jogador quando ele insere a resposta errada no chat
incorrect: '<prefix><red>Você inseriu uma resposta errada! Por favor, tente novamente.'
# Mensagem mostrada ao jogador quando ele está sendo verificado
# Defina isso como '' para desativar a mensagem de boas-vindas completamente
welcome: '<prefix><green>Você está sendo verificado. Por favor, não se mova; este é um processo automático.'
# Mensagem de desconexão que é mostrada quando alguém se junta enquanto o captcha não foi preparado ainda
currently-preparing:
- <header>
- <yellow>Seus dados anti-bot ainda não foram preparados.
- <gray>Por favor, aguarde alguns segundos antes de tentar verificar novamente.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta com uma versão do Minecraft não suportada
unsupported-version:
- <header>
- <red>Este servidor não suporta sua versão do Minecraft.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se reconecta muito rápido durante a verificação
too-fast-reconnect:
- <header>
- <gold>Você se reconectou muito rápido, tente novamente mais tarde.
- <gray>Por favor, aguarde alguns segundos antes de tentar verificar novamente.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta, mas já está verificando
already-verifying:
- <header>
- <red>Seu endereço IP está atualmente sendo verificado.
- <red>Por favor, aguarde alguns segundos antes de tentar verificar novamente.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta, mas já está na fila para verificação
already-queued:
- <header>
- <red>Seu endereço IP está atualmente na fila para verificação.
- <red>Por favor, aguarde alguns minutos antes de tentar verificar novamente.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta, mas está temporariamente na lista negra
blacklisted:
- <header>
- <red>Você está atualmente negado de entrar no servidor.
- <red>Por favor, aguarde alguns minutos para poder entrar no servidor novamente.
- <gold>Falso positivo? <gray><support-url>
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta com um nome de usuário inválido
invalid-username:
- <header>
- <red>Seu nome de usuário contém caracteres inválidos.
- <footer>
# Mensagem de desconexão que é mostrada quando alguém se junta com uma versão na lista negra
blacklisted-protocol:
- <header>
- <red>Você está usando uma versão que não é permitida em nosso servidor.
- <gold>Precisa de ajuda para fazer login? <gray><support-url>
- <footer>
# Mensagem de desconexão que é mostrada quando alguém verifica com sucesso
success:
- <header>
- <green>Você passou na verificação com sucesso.
- <white>Você agora pode jogar no servidor quando se reconectar.
# Mensagem de desconexão que é mostrada quando alguém falha na verificação
failed:
- <header>
- <red>Você falhou na verificação do bot.
- <gray>Por favor, aguarde alguns segundos antes de tentar verificar novamente.
- <gold>Precisa de ajuda para fazer login? <gray><support-url>
- <footer>
# Traduções para mensagens de verbose da actionbar do Sonar
verbose:
layout:
# Layout para o verbose da actionbar do Sonar
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Verificando <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
# Layout para o verbose da actionbar do Sonar durante um ataque
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Fila <white><queued> <dark_aqua>╺ <gray>Verificando <white><verifying> <dark_aqua>╺ <gray>Duração <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
# Animação para a action bar
# Alternativas:
# - ▙, ▛, ▜, ▟
# - ⬈, ⬊, ⬋, ⬉
animation:
- ◜
- ◝
- ◞
- ◟
# Traduções para mensagens de notificação de ataque do Sonar
notifications:
# Layout para o título da notificação de ataque do Sonar
title: <yellow><b>Sonar<reset>
# Layout para o subtítulo da notificação de ataque do Sonar
subtitle: <gray>Um ataque de bot foi detectado
# Layout para a notificação de ataque do Sonar no chat
chat:
- ''
- <yellow><b>Sonar<reset>
- ''
- <gray>Um ataque de bot foi detectado.
- ' <dark_aqua>▪ <gray>Logins por segundo: <white><logins-per-second>'
- ' <dark_aqua>▪ <gray>Jogadores verificando: <white><verifying>'
- ' <dark_aqua>▪ <gray>Jogadores na fila: <white><queued>'
- ''
- <gray>Veja mais informações usando <white><click:run_command:'/sonar verbose'><hover:show_text:'(Clique para executar)'>/sonar verbose</hover></click><gray> ou <white><click:run_command:'/sonar stats'><hover:show_text:'(Clique para executar)'>/sonar stats</hover></click><gray>.
- ''
@@ -0,0 +1,60 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Precisa de ajuda ou tem dúvidas? https://jonesdev.xyz/discord
# Código-fonte: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development e Sonar Contributors, licenciado sob GPL-3.0
#
# Notificações de ataque de bot também podem ser enviadas para o seu servidor Discord usando webhooks
webhook:
# URL do webhook do Discord (Defina como '' para desativar webhooks)
url: ''
# Nome de usuário do remetente do webhook do Discord
username: Sonar
# URL para o avatar do remetente do webhook do Discord (Defina como '' para desativar)
avatar-url: ''
# Conteúdo da mensagem do webhook do Discord (Defina como '' para desativar)
# Você pode usar isso para, por exemplo, pingar membros da equipe usando <@userId>
# Se você quiser pingar funções, precisará usar <@&roleId>
content: ''
# Integrar mensagem do webhook do Discord que é enviada quando um ataque é interrompido
embed:
# Mensagem de rodapé pequena do embed do webhook do Discord
footer:
# Conteúdo da mensagem de rodapé do embed do webhook do Discord
text: © Jones Development e Contribuidores do Sonar
# URL do ícone da mensagem de rodapé do embed do webhook do Discord
icon-url: ''
# Título do embed do webhook do Discord
title: ':white_check_mark: Ataque mitigado'
# URL clicável do título do embed do Discord
title-url: ''
# Descrição (conteúdo) do embed do webhook do Discord
description:
- O ataque ao seu servidor foi mitigado.
- ''
- 'Início do ataque: <t:<start-timestamp>:T>'
- 'Fim do ataque: <t:<end-timestamp>:T>'
- 'Duração do ataque: <attack-duration>'
- ''
- 'Pico de uso de CPU do processo durante o ataque: <peak-cpu>%'
- 'Pico de uso de memória do processo durante o ataque: <peak-memory>'
- 'Pico de bots por segundo durante o ataque: <peak-bps>'
- ''
- 'Endereços IP na lista negra durante o ataque: <total-blacklisted>'
- 'Verificações falhadas durante o ataque: <total-failed>'
- 'Verificações bem-sucedidas durante o ataque: <total-success>'
# Cores RGB do embed do webhook do Discord
# Seletor de cores: https://www.rapidtables.com/web/color/RGB_Color.html
color:
red: 0
green: 255
blue: 0
+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev
version=2.1.22
version=2.1.34
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+8 -8
View File
@@ -1,15 +1,15 @@
[versions]
netty = "4.1.116.Final"
adventure = "4.18.0"
adventure-platform = "4.3.4"
netty = "4.2.2.Final"
adventure = "4.21.0"
adventure-platform = "4.4.0"
bstats = "3.1.0"
libby = "2.0.0-SNAPSHOT"
pluginyml = "0.6.0"
[plugins]
indra-git = "net.kyori.indra.git:3.1.3"
shadow = "com.gradleup.shadow:8.3.5"
spotless = "com.diffplug.spotless:7.0.1"
shadow = "com.gradleup.shadow:8.3.7"
spotless = "com.diffplug.spotless:7.0.4"
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
@@ -28,11 +28,11 @@ bstats-bungee = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats
bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" }
bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" }
imagefilters = "com.jhlabs:filters:2.0.235-1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
annotations = "org.jetbrains:annotations:26.0.1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.1"
annotations = "org.jetbrains:annotations:26.0.2"
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
lombok = "org.projectlombok:lombok:1.18.36"
lombok = "org.projectlombok:lombok:1.18.38"
bungeecord = "net.md-5:bungeecord-proxy:1.21-SNAPSHOT"
velocity = "com.velocitypowered:velocity-proxy:3.4.0-SNAPSHOT"
# We have to use 1.8.8 for backwards compatibility
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -3
View File
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -206,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.