Compare commits

..
95 Commits
Author SHA1 Message Date
Jones b973f70441 Release 2.1.36 (#503) 2025-08-07 11:03:04 +00:00
dependabot[bot] 8d6a45c3b4 Bump net.kyori.indra.git from 3.1.3 to 3.2.0 (#501)
Bumps net.kyori.indra.git from 3.1.3 to 3.2.0.

---
updated-dependencies:
- dependency-name: net.kyori.indra.git
  dependency-version: 3.2.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-08-04 01:11:31 +00:00
jonesdevelopment 142ce80b9b Simplify max online per IP check 2025-07-31 20:10:39 +02:00
dependabot[bot] a8589e36e2 Bump com.diffplug.spotless from 7.2.0 to 7.2.1 (#498)
Bumps com.diffplug.spotless from 7.2.0 to 7.2.1.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 7.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-07-28 00:32:51 +00:00
Jones 26bee1ca0b 1.21.8 Support (server) 2025-07-22 01:19:23 +02:00
dependabot[bot] 9cfeb59604 Bump com.diffplug.spotless from 7.1.0 to 7.2.0 (#495)
Bumps com.diffplug.spotless from 7.1.0 to 7.2.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 7.2.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-07-21 07:15:11 +00:00
Jones 8464da8ef2 Update velocity-proxy dependency module 2025-07-14 21:58:35 +02:00
dependabot[bot] 5fa0814329 Bump com.diffplug.spotless from 7.0.4 to 7.1.0 (#493)
Bumps com.diffplug.spotless from 7.0.4 to 7.1.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 7.1.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-07-14 14:21:18 +00:00
dependabot[bot] 59332b8134 Bump com.github.ben-manes.caffeine:caffeine from 3.2.1 to 3.2.2 (#492)
Bumps [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: com.github.ben-manes.caffeine:caffeine
  dependency-version: 3.2.2
  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-07-14 14:21:10 +00:00
Jones 3ca1852e03 Release 2.1.35 (#491) 2025-07-09 16:33:22 +00:00
Jones 625a3a1e8e Bump Gradle from 8.14.2 to 8.14.3 2025-07-07 12:01:45 +02:00
dependabot[bot] 646f8f08f6 Bump com.gradleup.shadow from 8.3.7 to 8.3.8 (#490)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.7 to 8.3.8.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.7...8.3.8)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-version: 8.3.8
  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-07-07 09:56:30 +00:00
Jones 947eb7f6a1 Remove locale regex check due to a Minecraft bug
There's a bug in Minecraft where, when no language seems to be selected, the client may send invalid data to the server. As the length of the locale string is already capped in Sonar either way, we are not required to double-check the decoded string using a regex.
2025-07-06 20:10:20 +02:00
Jones 881f826692 Add missing translations for the new feature 2025-07-05 12:44:07 +02:00
Jones 4252362584 Make incorrect captcha message contain attempts left 2025-07-05 12:40:07 +02:00
Jones 7271a2a13f Allow "fell through" check to be skipped by a CAPTCHA 2025-07-03 22:02:28 +02:00
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
100 changed files with 1824 additions and 611 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");
@@ -177,7 +177,7 @@ public final class SonarConfiguration {
verification.map.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.map-captcha.timing"));
verification.map.precomputeAmount = clamp(generalConfig.getInt("verification.checks.map-captcha.precompute"), 10, 5000);
verification.map.maxDuration = clamp(generalConfig.getInt("verification.checks.map-captcha.max-duration"), 5000, 360000);
verification.map.maxTries = generalConfig.getInt("verification.checks.map-captcha.max-tries");
verification.map.maxTries = clamp(generalConfig.getInt("verification.checks.map-captcha.max-tries"), 1, 100);
verification.map.alphabet = generalConfig.getString("verification.checks.map-captcha.alphabet");
verification.map.backgroundImage = null;
@@ -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());
}
}
@@ -198,7 +198,6 @@ public final class SonarConfiguration {
verification.timeOfDay = clamp(generalConfig.getInt("verification.time-of-day"), 0, 24000);
verification.gamemode = Verification.Gamemode.valueOf(generalConfig.getString("verification.gamemode"));
verification.validNameRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-name-regex"));
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
verification.checkGeyser = generalConfig.getBoolean("verification.check-geyser-players");
verification.logConnections = generalConfig.getBoolean("verification.log-connections");
@@ -288,7 +287,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 +296,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;
}
@@ -418,7 +417,6 @@ public final class SonarConfiguration {
private boolean logDuringAttack;
private boolean debugXYZPositions;
private Pattern validNameRegex;
private Pattern validLocaleRegex;
private int readTimeout;
private int writeTimeout;
@@ -447,14 +445,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,11 @@ 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,
MINECRAFT_1_21_8;
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,30 +168,21 @@ 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();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
final int maxOnlinePerIp = Sonar.get0().getConfig().getMaxOnlinePerIp();
final int newCount = Sonar.get0().getFallback().getOnline().compute(inetAddress,
(__, count) -> count == null ? 1 : count + 1);
if (newCount > maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return;
}
loginPacket.run();
}
/**
* 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 +193,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 +224,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 +249,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()));
@@ -96,11 +108,11 @@ public class FallbackPreparer {
public static final FallbackPacket REMOVE_VEHICLE = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
public static final FallbackPacket SET_VEHICLE_PASSENGERS = new FallbackPacketSnapshot(
new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
public FallbackPacket[] incorrectCaptcha;
public FallbackPacket loginSuccess;
public FallbackPacket welcomeMessage;
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
public static FallbackPacket joinGame;
public FallbackPacket defaultSpawnPosition;
public FallbackPacket spawnPosition;
@@ -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,32 @@ 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())))));
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.enter"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
incorrectCaptcha = new FallbackPacket[Sonar.get0().getConfig().getVerification().getMap().getMaxTries()];
for (int i = 0; i < incorrectCaptcha.length; i++) {
incorrectCaptcha[i] = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix()),
Placeholder.unparsed("attempts-left", String.valueOf(i + 1))))));
}
// 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 +256,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 +278,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) {
@@ -79,11 +79,11 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
// Decrement the number of tries left
checkState(tries-- > 0, "failed CAPTCHA too often");
// Send the player a chat message to let them know that the code they entered is incorrect
user.write(incorrectCaptcha);
user.write(incorrectCaptcha[tries]);
} 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,14 +192,15 @@ 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);
}
// Ensure that the player's Y coordinate is above the collision platform
checkState(y >= PLATFORM_Y_POSITION,
"fell through blocks: " + y + "/" + deltaY + "/" + movementTick);
if (y < PLATFORM_Y_POSITION) {
failOrShowCaptcha("fell through blocks: " + y + "/" + deltaY + "/" + movementTick);
}
// The player is not allowed to move away from the collision platform.
// This should not happen unless the max movement tick is configured to a high number.
@@ -262,7 +267,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");
@@ -106,17 +106,12 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
checkState(clientInformation.getViewDistance() >= 2,
"view distance: " + clientInformation.getViewDistance());
}
// Ensure that the client locale is correct
validateClientLocale(clientInformation.getLocale());
// Check if the player sent an unused bit flag in the skin section
// 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 +120,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 +139,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 +159,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 +172,16 @@ 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();
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
@@ -133,13 +136,11 @@ verification:
# Regex pro validování jmen při verifikaci
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex pro validaci "client locale" při verifikaci
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +185,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
@@ -130,13 +133,11 @@ verification:
# Regex zur Validierung von Benutzernamen während der Überprüfung
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex zur Validierung der Client-Spracheinstellung während der Überprüfung
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +182,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
@@ -133,13 +136,11 @@ verification:
# Regex for validating usernames during verification
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex for validating client locale during verification
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +185,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
@@ -132,13 +135,11 @@ verification:
# Regex pour valider les noms d'utilisateur lors de la vérification
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex pour valider la locale client lors de la vérification
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +184,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 -16
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
@@ -132,20 +135,18 @@ verification:
# Regex-ი მომხმარებლის სახელების ვერიფიკაციისთვის
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex-ი კლიენტის ადგილის დასადასტურებლად ვერიფიკაციის დროს
valid-locale-regex: ^[a-zA-Z_]+$
# თუ ჩართულია, მოთამაშე გადავა საწყის სერვერში ვერიფიკაციის წარმატებით გავლის შემდეგ
# ეს ფუნქცია დაემატა 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 +163,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 +183,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
@@ -132,13 +135,11 @@ verification:
# Regex voor het valideren van gebruikersnamen tijdens verificatie
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex voor het valideren van de landinstelling van de client tijdens verificatie
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +184,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
@@ -132,13 +135,11 @@ verification:
# Regex do sprawdzania poprawności nazw użytkowników podczas weryfikacji
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex do sprawdzania ustawień regionalnych klienta podczas weryfikacji
valid-locale-regex: ^[a-zA-Z_]+$
# 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 +184,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,210 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# 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_]+$
# 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
@@ -131,13 +134,11 @@ verification:
# Regex для проверки ников при верификации
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex для проверки локализации клиента при верификации
valid-locale-regex: ^[a-zA-Z_]+$
# Если включено, игрок будет возвращен на исходный сервер
# после успешного прохождения проверки на бота.
# Эта функция была введена 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 +183,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 应该给玩家多少时间完成验证码?
@@ -130,12 +133,10 @@ verification:
# 用于检查用户名是否有效的正则表达式
valid-name-regex: ^[a-zA-Z0-9_]+$
# 用于检查客户端语言(例如 zh_CN)的正则表达式
valid-locale-regex: ^[a-zA-Z_]+$
# 开启后 玩家将在验证成功后被重定向到指定的服务器
# 该功能由 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 +180,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
@@ -196,7 +196,7 @@ verification:
# Zpráva zobrazená hráči, když má zadat odpověď na captchu
enter: '<prefix><green>Prosím, zadejte text do chatu, který se zobrazí na mapě.'
# Zpráva zobrazená hráči, když zadá špatnou odpověď do chatu
incorrect: '<prefix><red>Zadali jste špatnou odpověď! Zkuste to prosím znovu.'
incorrect: '<prefix><red>Zadali jste špatnou odpověď! Zbývají vám <attempts-left> pokus(y).'
# Zpráva zobrazená hráči při ověřování
# Nastavením této hodnoty na '' uvítací zprávu zcela vypnete
@@ -197,7 +197,7 @@ verification:
# Nachricht, die dem Spieler angezeigt wird, wenn er den CAPTCHA-Code im Chat eingeben muss
enter: '<prefix><green>Bitte gib den Text im Chat ein, der auf der Karte angezeigt wird.'
# Nachricht, die dem Spieler angezeigt wird, wenn er den falschen Code im Chat eingibt
incorrect: '<prefix><red>Du hast eine falsche Antwort eingegeben! Bitte versuche es erneut.'
incorrect: '<prefix><red>Du hast eine falsche Antwort eingegeben! Sie haben noch <attempts-left> Versuch(e) übrig.'
# Nachricht, die dem Spieler angezeigt wird, wenn er überprüft wird
# Setzen Sie dies auf '', um die Willkommensnachricht vollständig zu deaktivieren
@@ -196,7 +196,7 @@ verification:
# Message shown to the player when they have to enter the answer to the CAPTCHA
enter: '<prefix><green>Please enter the text in chat that is displayed on the map.'
# Message shown to the player when they enter the wrong answer in chat
incorrect: '<prefix><red>You have entered a wrong answer! Please try again.'
incorrect: '<prefix><red>You have entered a wrong answer! You have <attempts-left> attempt(s) left.'
# Message shown to the player when they are being verified
# Set this to '' to disable the welcome message entirely
@@ -196,7 +196,7 @@ verification:
# Message shown to the player when they have to enter the answer to the captcha
enter: '<prefix><green>Por favor, introduce en el chat el texto que se muestra en el mapa.'
# Message shown to the player when they enter the wrong answer in chat
incorrect: '<prefix><red>¡Has introducido una respuesta incorrecta! Por favor, inténtalo de nuevo.'
incorrect: '<prefix><red>¡Has introducido una respuesta incorrecta! Te quedan <attempts-left> intento(s).'
# Mensaje que se muestra al jugador cuando está siendo verificado
# Ajústalo a '' para desactivar por completo el mensaje de bienvenida
@@ -196,7 +196,7 @@ verification:
# Message affiché au joueur lorsqu'il doit entrer la réponse au CAPTCHA
enter: '<prefix><green>Veuillez saisir le texte en chat qui s''affiche sur la carte.'
# Message affiché au joueur lorsqu'il entre la mauvaise réponse dans le chat
incorrect: '<prefix><red>Vous avez saisi une mauvaise réponse ! Veuillez réessayer.'
incorrect: '<prefix><red>Vous avez saisi une mauvaise réponse ! Il vous reste <attempts-left> tentative(s).'
# Message affiché au joueur lorsqu'il est en cours de vérification
# Réglez ce paramètre sur '' pour désactiver complètement le message de bienvenue
@@ -196,7 +196,7 @@ verification:
# Pesan yang ditampilkan kepada pemain saat mereka harus memasukkan jawaban untuk CAPTCHA
enter: '<prefix><green>Masukkan teks dalam obrolan yang ditampilkan di peta.'
# Pesan yang ditampilkan kepada pemain saat mereka memasukkan jawaban yang salah di chat
incorrect: '<prefix><red>Anda telah memasukkan jawaban yang salah! Silakan coba lagi.'
incorrect: '<prefix><red>Anda telah memasukkan jawaban yang salah! Anda memiliki <attempts-left> percobaan yang tersisa.'
# Pesan yang ditampilkan kepada pemain ketika mereka sedang diverifikasi
# Atur ini ke '' untuk menonaktifkan pesan selamat datang sepenuhnya
@@ -196,7 +196,7 @@ verification:
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა მიუთითონ CAPTCHA ს პასუხი
enter: '<prefix><green>გთხოვთ დაწეროთ ჩატში ის კოდი რომელიც არის ნაჩვენები რუკაზე.'
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა ისინი ჩაწერენ არასწორ პასუხს ჩატში
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. გთხოვთ ხელახლა ცადოთ.'
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. თქვენ დაგრჩათ <attempts-left> მცდელობა(ები).'
# Message shown to the player when they are being verified
# Set this to '' to disable the welcome message entirely
@@ -196,7 +196,7 @@ verification:
# Bericht dat aan de speler wordt getoond wanneer hij het antwoord op de CAPTCHA moet invoeren
enter: '<prefix><green>Voer in de chat de tekst in die op de kaart wordt weergegeven.'
# Bericht dat aan de speler wordt getoond wanneer hij of zij het verkeerde antwoord invoert in de chat
incorrect: '<prefix><red>Je hebt een verkeerd antwoord ingevoerd! Probeer het opnieuw.'
incorrect: '<prefix><red>Je hebt een verkeerd antwoord ingevoerd! Je hebt <attempts-left> poging(en) over.'
# Bericht dat aan de speler wordt getoond als hij wordt geverifieerd
# Stel dit in op '' om het welkomstbericht volledig uit te schakelen
@@ -199,7 +199,7 @@ verification:
# Message shown to the player when they have to enter the answer to the captcha
enter: '<prefix><green>Wprowadź na czacie tekst wyświetlany na mapie.'
# Message shown to the player when they enter the wrong answer in chat
incorrect: '<prefix><red>Wprowadzono nieprawidłowy kod. Spróbuj ponownie.'
incorrect: '<prefix><red>Wprowadzono nieprawidłowy kod. Zostało <attempts-left> prób.'
# Wiadomość wyświetlana graczowi podczas weryfikacji
# Ustaw wartość '', aby całkowicie wyłączyć wiadomość powitalną
@@ -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! Você tem <attempts-left> tentativa(s) restante(s).'
# 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>.
- ''
@@ -196,7 +196,7 @@ verification:
# Сообщение, которое показывается игроку, когда он должен ввести ответ на капчу
enter: '<prefix><green>Пожалуйста, введите в чат текст, который будет отображаться на карте.'
# Сообщение, которое показывается игроку, когда он вводит неправильную капчу в чат
incorrect: '<prefix><red>Вы ввели неправильный ответ! Пожалуйста, попробуйте еще раз.'
incorrect: '<prefix><red>Вы ввели неверный код! У вас осталось <attempts-left> попыток.'
# Сообщение, показываемое игроку при прохождении верификации
# Установите значение '', чтобы полностью отключить приветственное сообщение
@@ -196,7 +196,7 @@ verification:
# CAPTCHA'nın cevabını girmeleri gerektiğinde oyuncuya gösterilen mesaj
enter: '<prefix><green>Lütfen haritada görüntülenen sohbet metnini girin.'
# Message that is shown to the player when they enter the wrong answer in chat
incorrect: '<prefix><red>Yanlış bir cevap girdiniz! Lütfen tekrar deneyin.'
incorrect: '<prefix><red>Yanlış bir cevap girdiniz! <attempts-left> deneme(ler)iniz kaldı.'
# Doğrulandıklarında oyuncuya gösterilen mesaj
# Karşılama mesajını tamamen devre dışı bırakmak için bunu '' olarak ayarlayın
@@ -196,7 +196,7 @@ verification:
# 玩家需要输入验证码答案时显示的消息
enter: '<prefix><green>请输入地图上显示的聊天文本.'
# 当玩家输入错误的验证码时显示的消息
incorrect: '<prefix><red>您输入了错误的答案! 请重试.'
incorrect: '<prefix><red>您输入了错误的答案! 您还剩 <attempts-left> 次尝试。'
# 当玩家正在接受验证时显示的信息
# 将此项设置为''可完全禁用加入时发送的消息
@@ -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.36
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+10 -10
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"
indra-git = "net.kyori.indra.git:3.2.0"
shadow = "com.gradleup.shadow:8.3.8"
spotless = "com.diffplug.spotless:7.2.1"
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
@@ -28,12 +28,12 @@ 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.2"
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"
velocity = "io.papermc.velocity:velocity-proxy:3.4.0-SNAPSHOT"
# We have to use 1.8.8 for backwards compatibility
spigot = "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
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.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+4 -5
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
@@ -115,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -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.
@@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -2
View File
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell