Compare commits

..
231 Commits
Author SHA1 Message Date
NotAlexNoyle a63a50f1cf Exclude CODEOWNERS from language statistics 2026-10-26 12:28:35 -04:00
NotAlexNoyle b34161bb49 Add CODEOWNERS (one staff approval) 2026-10-26 05:47:04 -04:00
Jan1k 3dbc108b8d Fix message placeholder keys (#592) 2026-06-14 20:12:34 +02:00
Jan1k 781d30eb43 Fix Turkish subcommand formatting (#591) 2026-06-14 17:42:19 +02:00
dependabot[bot] 518e602bf3 Bump io.netty:netty-all from 4.2.14.Final to 4.2.15.Final (#590)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.14.Final to 4.2.15.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.2.14.Final...netty-4.2.15.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.15.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>
2026-06-08 09:59:56 +02:00
Jones d62f0440ad [ci skip] Add LLM Guidelines to CONTRIBUTING.md 2026-06-06 17:25:37 +02:00
MISHA bf29fa5ca7 Add forced hosts support to transfer feature (#588)
* feat: add use-handshake-hostname config option for forced hosts support

* Solve issues
2026-06-03 21:14:43 +02:00
dependabot[bot] 104691b816 Bump com.diffplug.spotless from 8.5.1 to 8.6.0 (#586)
Bumps com.diffplug.spotless from 8.5.1 to 8.6.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.6.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>
2026-06-01 13:58:37 +02:00
dependabot[bot] 097816760f Bump io.netty:netty-all from 4.2.13.Final to 4.2.14.Final (#583)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.13.Final to 4.2.14.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.2.13.Final...netty-4.2.14.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.14.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>
2026-05-25 15:08:46 +02:00
dependabot[bot] 5f1b040c99 Bump com.diffplug.spotless from 8.4.0 to 8.5.1 (#580)
Bumps com.diffplug.spotless from 8.4.0 to 8.5.1.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.5.1
  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>
2026-05-18 17:04:11 +02:00
dependabot[bot] 911a655aaf Bump gradle-wrapper from 9.5.0 to 9.5.1 (#582)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.0 to 9.5.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.0...v9.5.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.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>
2026-05-18 17:03:55 +02:00
dependabot[bot] 82133e7ac7 Bump io.netty:netty-all from 4.2.12.Final to 4.2.13.Final (#579)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.12.Final to 4.2.13.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.2.12.Final...netty-4.2.13.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.13.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>
2026-05-11 19:57:42 +02:00
Jones d85f2bc2d5 Release 2.1.47 (#578) 2026-05-09 13:47:52 +02:00
Jones 877e3630b7 Warn if the default support URL is used 2026-05-08 21:50:21 +02:00
Jones 0c4495e502 Automatically replace legacy support URL 2026-05-08 21:46:05 +02:00
Jones 8cb92bb074 Make individual check timings configurable 2026-05-08 21:45:19 +02:00
dependabot[bot] 89a07cd06e Bump com.github.ben-manes.caffeine:caffeine from 3.2.3 to 3.2.4 (#577)
Bumps [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.3...v3.2.4)

---
updated-dependencies:
- dependency-name: com.github.ben-manes.caffeine:caffeine
  dependency-version: 3.2.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>
2026-05-07 13:45:11 +02:00
dependabot[bot] 698a611511 Bump gradle-wrapper from 9.4.1 to 9.5.0 (#576)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.1 to 9.5.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.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>
2026-05-05 17:09:26 +02:00
Jones 0f598bb1da Implement option to place map in offhand (https://github.com/jonesdevelopment/sonar/issues/574) 2026-04-29 17:39:11 +02:00
dependabot[bot] 1a8c1caa39 Bump org.projectlombok:lombok from 1.18.44 to 1.18.46 (#573)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.44 to 1.18.46.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.44...v1.18.46)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.46
  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>
2026-04-27 09:31:57 +02:00
Jones c24fe4a4d9 [ci skip] Update homepage and documentation links 2026-04-26 15:15:46 +02:00
Jones 2da0cc6761 Use JdbcConnectionSource for H2 2026-04-24 23:51:22 +02:00
Jones 80b7726f18 Update security contact email 2026-04-23 20:45:37 +02:00
Jones 9bab457d58 Release 2.1.46 (#569) 2026-03-30 19:06:25 +02:00
Jones 37d294afe3 26.1 Support (#568)
* Add 26.1 protocol versions

* Add 26.1 block state IDs

* Add 26.1 codec

* Add missing tags for 26.1

* Fix chunk data on 26.1

* Fix SetContainerSlotPacket on 26.1

* Fix map item type and components on 26.1

* Fix UpdateTimePacket

* Fix ChunkDataPacket for <26.1
2026-03-30 19:03:04 +02:00
dependabot[bot] 6ea9c1dcba Bump com.gradleup.shadow from 8.3.8 to 9.4.1 (#566)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.8 to 9.4.1.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.8...9.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 15:29:18 +02:00
dependabot[bot] 1f98050abb Bump io.netty:netty-all from 4.2.10.Final to 4.2.12.Final (#567)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.10.Final to 4.2.12.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.12.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.12.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>
2026-03-30 15:28:54 +02:00
Jones cea1ac00f0 Include Paper artifact in upload task 2026-03-29 00:14:55 +01:00
Jones 620aaa3ff2 Bump bungeecord-proxy from 1.21-SNAPSHOT to 26.1-R0.1-SNAPSHOT 2026-03-29 00:12:28 +01:00
dependabot[bot] 381ad66339 Bump gradle-wrapper from 9.3.1 to 9.4.1 (#564)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.3.1 to 9.4.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.3.1...v9.4.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.4.1
  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>
2026-03-23 14:27:12 +01:00
dependabot[bot] de70d23c54 Bump com.diffplug.spotless from 8.3.0 to 8.4.0 (#565)
Bumps com.diffplug.spotless from 8.3.0 to 8.4.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.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>
2026-03-23 14:27:01 +01:00
dependabot[bot] b49474a56f Bump org.projectlombok:lombok from 1.18.42 to 1.18.44 (#563)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.42 to 1.18.44.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.42...v1.18.44)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.44
  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>
2026-03-16 07:31:29 +01:00
dependabot[bot] 058f034245 Bump com.diffplug.spotless from 8.2.1 to 8.3.0 (#560)
Bumps com.diffplug.spotless from 8.2.1 to 8.3.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.3.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>
2026-03-09 19:24:43 +01:00
Jones d94333eed7 Make update checker errors more human-readable (https://github.com/jonesdevelopment/sonar/issues/561) 2026-03-09 19:23:44 +01:00
dependabot[bot] 2550dc9fbf Bump bstats from 3.1.0 to 3.2.1 (#557)
Bumps `bstats` from 3.1.0 to 3.2.1.

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

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

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

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-bukkit
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-velocity
  dependency-version: 3.2.1
  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>
2026-03-02 13:32:48 +01:00
Jones 8be3d974e4 Add sonar.log-netty-exceptions flag 2026-02-26 20:07:22 +01:00
dependabot[bot] cab082cdd5 Bump org.jetbrains:annotations from 26.0.2 to 26.1.0 (#556)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 26.0.2 to 26.1.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/26.0.2...26.1.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-version: 26.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>
2026-02-23 15:15:34 +01:00
Jones 5a65dbcdb9 Update license year in file header 2026-02-17 19:11:23 +01:00
dependabot[bot] 0ca1ecd421 Bump io.netty:netty-all from 4.2.7.Final to 4.2.10.Final (#554)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.7.Final to 4.2.10.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.10.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.10.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>
2026-02-09 14:17:27 +01:00
Jones c6ec0da14c Make README documentation link more obvious 2026-02-06 17:56:06 +01:00
Jones 76bcfc8b9a Use correct, maintained Discord link 2026-02-05 15:32:15 +01:00
Jones 70506aaf64 Release 2.1.45 (#553) 2026-02-02 14:58:32 +01:00
Jones 873d9f8c10 Use JDK 21 for GitHub Actions 2026-02-02 14:55:08 +01:00
Jones 8f40552dc5 Replace broken sonatype OSS repository link 2026-02-02 14:50:52 +01:00
dependabot[bot] 6302888a33 Bump gradle-wrapper from 9.3.0 to 9.3.1 (#552)
Bumps gradle-wrapper from 9.3.0 to 9.3.1.

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.3.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>
2026-02-02 14:46:37 +01:00
dependabot[bot] ea7cc9a9f3 Bump com.diffplug.spotless from 8.2.0 to 8.2.1 (#551)
Bumps com.diffplug.spotless from 8.2.0 to 8.2.1.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.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>
2026-02-02 14:46:27 +01:00
Jones ea928ce6db Add experimental config option for checking Eagler players 2026-02-01 10:29:17 +01:00
Jones d19ee78c41 Bump velocity-proxy from 3.4.0-SNAPSHOT to 3.5.0-SNAPSHOT 2026-02-01 10:20:10 +01:00
Jones 924f4e7344 Bump caffeine from 3.2.2/3.2.0 to 3.2.3 2026-02-01 10:15:54 +01:00
dependabot[bot] 6e4c938767 Bump gradle-wrapper from 8.14.3 to 9.3.0 (#549)
Bumps gradle-wrapper from 8.14.3 to 9.3.0.

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 10:08:06 +01:00
dependabot[bot] e534738336 Bump com.diffplug.spotless from 7.2.1 to 8.2.0 (#550)
Bumps com.diffplug.spotless from 7.2.1 to 8.2.0.

---
updated-dependencies:
- dependency-name: com.diffplug.spotless
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 16:05:09 +01:00
bobhenl 92d40ca4cf Fix skipping EaglerCraft players & error on join (#547)
* fix: fix skipping EaglerCraft players & error on join

* docs: improve eagler comment
2026-01-15 21:00:12 +01:00
Jones 0fd7f0c173 Release 2.1.44 (#545) 2026-01-10 22:25:43 +01:00
Jones f197260426 Always skip EaglerCraft players 2026-01-10 22:10:15 +01:00
Squesly cbae477982 Fix connection has already been closed (#544)
* Fixed a MySQL issue: connection has already been closed.

* Fixed import
2026-01-10 09:55:51 +01:00
JohnandJones 878789b2c1 Add Paper platform (#541)
* Add paper platform

* Fix package name for Paper module

* Load adventure-nbt library on paper

* Add missing license header

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2026-01-05 15:04:59 +01:00
Jones 2a98d4bdb0 Remove mentions of nonexistent tests in dependencies 2026-01-01 13:37:17 +01:00
Jones 790d233870 Make LibraryLoader use correct MiniMessage versions 2026-01-01 13:10:54 +01:00
Jones e732da6af2 [skip ci] Update sponsors 2025-12-27 00:42:30 +01:00
Jones e72e064dcc Release 2.1.43 (#540) 2025-12-23 20:22:55 +01:00
Jones 4dcafc9c91 Remove leftover mentions of Sonar's "Fallback" 2025-12-23 20:18:02 +01:00
Jones 7f2e910c2d Log full stacktraces in UpdateChecker 2025-12-23 14:36:20 +01:00
Jones b391ce38fb Refactor Sonar to stop using the "Fallback" branding 2025-12-23 14:34:17 +01:00
Jones 261f8e20e8 Remove "duplicate slot" check for stability reasons 2025-12-23 14:27:12 +01:00
Jones 4c1c509bfa Implement Italian translations (thanks to Augu0107) 2025-12-16 15:08:28 +01:00
Jones a5948bc494 Fix client brand regex issues with norisk client 2025-12-15 21:36:30 +01:00
Jones de5750e4eb Release 2.1.42 (#535) 2025-12-09 18:17:10 +00:00
Jones 71b4bad41c 1.21.11 Support (#534)
* [wip] Add 1.21.11 protocol version

* [wip] Add 1.21.11 codec

* [wip] Add 1.21.11 entity mappings

* [wip] Add 1.21.11 item component mappings
2025-12-09 18:13:32 +00:00
Jones 05d2e55d69 Minor codestyle improvements 2025-12-02 21:36:36 +01:00
Jones 41fe39cf0f Fix incorrect file license header 2025-12-02 21:35:24 +01:00
Jones fa2e68be20 Implement complex CAPTCHA design (PoC) 2025-12-02 21:34:23 +01:00
Jones 2139147952 Update sponsors 2025-11-16 13:47:40 +01:00
Jones df049a7042 Release 2.1.41 (#528) 2025-10-27 13:53:35 +00:00
Jones 8df7874cab Don't invoke initChannel if the channel is closed (https://github.com/jonesdevelopment/sonar/pull/527) 2025-10-27 14:24:29 +01:00
Jones b132fe839e Disable max-online-per-ip if it's 0 or negative 2025-10-23 16:47:28 +02:00
Jones f0bdfa8ef1 Fix filled_map item type id on 1.21.9+ 2025-10-23 13:59:01 +02:00
dependabot[bot] 7899569970 Bump io.netty:netty-all from 4.2.6.Final to 4.2.7.Final (#526)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.6.Final to 4.2.7.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.7.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-10-20 15:24:04 +00:00
Jones 16d72fc682 Release 2.1.40 (#524) 2025-10-17 12:51:28 +00:00
Giuseppe PerrottaandJones 425a902ec8 Introduce PostgreSQL (#522)
* Introduce PostgreSQL

* update comment

* Mention `POSTGRESQL` in the configuration

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2025-10-07 13:05:35 +00:00
Jones fcb52fc6c6 Release 2.1.39 (#521) 2025-10-04 08:08:05 +00:00
Jones ac4d566627 Use radial gradient for CAPTCHA text 2025-10-03 11:32:05 +02:00
Jones 0944701797 1.21.9 Support (#520)
* [wip] 1.21.9 Support

* Implement missing BlockType mappings

* Implement missing EntityType mappings

* Implement less-precision vectors for SpawnEntityPacket

* Fix SpawnEntityPacket encoding the wrong values
2025-10-03 08:44:16 +00:00
Jones 9464bffd8e Use Spigot 1.16.5 for better compatibility 2025-10-03 10:26:53 +02:00
Jones 9299edc839 Include font license for kingthings fonts 2025-09-30 20:01:26 +02:00
dependabot[bot] 41eb6d9767 Bump org.projectlombok:lombok from 1.18.40 to 1.18.42 (#517)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.40 to 1.18.42.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.40...v1.18.42)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.42
  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-09-22 19:15:17 +00:00
dependabot[bot] 4289b92ab6 Bump io.netty:netty-all from 4.2.5.Final to 4.2.6.Final (#516)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.5.Final to 4.2.6.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.5.Final...netty-4.2.6.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.6.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-09-15 10:55:41 +00:00
Jones b2dddfb0a4 Release 2.1.38 (#515) 2025-09-13 15:12:37 +00:00
Jones ce2fca72f0 Update Dutch translations 2025-09-13 17:05:39 +02:00
Greened 87da706508 Update ka.yml (#514)
* Update ka.yml

* Update ka.yml
2025-09-13 09:30:40 +00:00
Jones 3480bf9332 Make max packet count configurable 2025-09-12 16:59:46 +02:00
Jones 745d01b4d2 Disable expected position but got client tick end check 2025-09-12 16:23:11 +02:00
Jones 4f0d70650b Release 2.1.37 (#511) 2025-09-09 14:54:49 +00:00
AkkariinandJones 2ad0ce11c0 More strict client tick check for 1.21.2+ clients (#496)
* More strict client tick check for 1.21.2+ clients

* [ci skip] fix code style

* Recode `FallbackVehicleHandler`

* [ci skip] fix code style

* Revert "Recode `FallbackVehicleHandler`"

This reverts commit 7eb02ced

* Use ProtocolVersion#equals instead of `==`

* Force-reset `tickWithoutMove` after every move

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2025-09-09 12:23:58 +00:00
dependabot[bot] fd1e5c9d72 Bump org.projectlombok:lombok from 1.18.38 to 1.18.40 (#508)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.38 to 1.18.40.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.38...v1.18.40)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-version: 1.18.40
  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-09-08 05:09:27 +00:00
dependabot[bot] e036ce421d Bump io.netty:netty-all from 4.2.4.Final to 4.2.5.Final (#509)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.4.Final to 4.2.5.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.5.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.5.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-09-08 05:09:15 +00:00
Jones cf2628a41f Add notice about Sonar 3.0 to README 2025-09-07 23:03:48 +02:00
Jones fcea8e5f11 Make font loading error slightly more descriptive 2025-09-02 11:51:06 +02:00
dependabot[bot] ad5de38b1d Bump adventure from 4.21.0 to 4.24.0 (#500)
Bumps `adventure` from 4.21.0 to 4.24.0.

Updates `net.kyori:adventure-text-minimessage` from 4.21.0 to 4.24.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.21.0...v4.24.0)

Updates `net.kyori:adventure-text-serializer-gson` from 4.21.0 to 4.24.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.21.0...v4.24.0)

Updates `net.kyori:adventure-nbt` from 4.21.0 to 4.24.0
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.21.0...v4.24.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-minimessage
  dependency-version: 4.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-text-serializer-gson
  dependency-version: 4.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-nbt
  dependency-version: 4.24.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-31 18:52:33 +00:00
dependabot[bot] bc0661c10c Bump adventure-platform from 4.4.0 to 4.4.1 (#502)
Bumps `adventure-platform` from 4.4.0 to 4.4.1.

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

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

---
updated-dependencies:
- dependency-name: net.kyori:adventure-platform-bukkit
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: net.kyori:adventure-platform-bungeecord
  dependency-version: 4.4.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-08-31 18:50:18 +00:00
Jones 5e233b4f73 Revert "Refactor vehicle check (#497)"
This reverts commit e7f816fcaa.
2025-08-31 15:43:57 +02:00
AkkariinandJones e7f816fcaa Refactor vehicle check (#497)
* Recode `FallbackVehicleHandler`

* Don't hard-fail in FallbackVehicleHandler#handleMovement

* Keep codestyle consistent with KeepAlive packet

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2025-08-26 10:29:49 +00:00
dependabot[bot] d87b775b76 Bump io.netty:netty-all from 4.2.2.Final to 4.2.4.Final (#506)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.2.2.Final to 4.2.4.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.2.2.Final...netty-4.2.4.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-version: 4.2.4.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-08-18 07:52:26 +00:00
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
Jones ab99061b93 Release 2.1.22 (#434) 2025-01-18 11:16:27 +01:00
Jones 7d7f1d97d9 Fix GitHub workflow build error 2025-01-16 17:08:15 +01:00
JonesandAkkariin 9f4cc49cf7 Use full registries to fix compatibility (#429)
* Add full registries to fix compatibility issues

* Fix 1.18.2-1.19.1 registries

* Fix codec nbt loading error

* Optimize 1.20-1.21.4 codecs

* Registries fixes & Fixed some unexpected behavior about mods. (#430)

* Fix 1.20.5 + viafabricplus

* Update registries

* Apply SIMPLE_BIOME filter on 1.18.2-1.21.4 codecs

* Use 1.21-1.21.4 codecs from the previous commit

---------

Co-authored-by: Akkariin <fallencrystal@outlook.com>
2025-01-16 16:50:31 +01:00
Jones 8531dcc58a Downgrade mariadb-java-client to 3.4.1 2025-01-15 19:34:57 +01:00
dependabot[bot] ec498000ba Bump com.diffplug.spotless from 7.0.0.BETA4 to 7.0.1 (#432)
Bumps com.diffplug.spotless from 7.0.0.BETA4 to 7.0.1.

---
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-13 14:44:52 +01:00
Jones 9ac46ce5a4 Bump Gradle from 8.11.1 to 8.12 2025-01-11 14:25:00 +01:00
Jones 7848e47990 Fix invalid block state ids for 1.21.4 2025-01-04 05:51:11 +01:00
Jones e377644e89 Update license year 2025-01-03 05:09:58 +01:00
Jones 9f377780cb Release 2.1.21 (#427) 2025-01-01 20:21:59 +01:00
Jones f14c5b2b90 Fix NoClassDefFoundError on BungeeCord and Bukkit 2025-01-01 20:20:13 +01:00
Jones 45683040ab Release 2.1.20 (#426) 2024-12-31 17:49:13 +01:00
Jones d0f63bea48 Update 1.19-1.21.2 codecs
* Backport codecs from Sonar 3.0
2024-12-31 17:46:48 +01:00
Jones 030a532df8 Remove some misleading TODOs 2024-12-30 05:27:24 +01:00
Jones 0f957b444b Bump dynamic adventure libraries 2024-12-23 13:42:48 +01:00
dependabot[bot] cb03833a8a Bump adventure from 4.17.0 to 4.18.0 (#422)
Bumps `adventure` from 4.17.0 to 4.18.0.

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

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

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

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-minimessage
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-text-serializer-gson
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.kyori:adventure-nbt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-23 13:42:16 +01:00
dependabot[bot] 22f2ab56bc Bump io.netty:netty-all from 4.1.115.Final to 4.1.116.Final (#423)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.115.Final to 4.1.116.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.115.Final...netty-4.1.116.Final)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-23 05:18:32 +01:00
Jones cda6b395ac Make ComponentHolder#serialize static 2024-12-20 16:50:31 +01:00
Jones 46c5853f7c Remove mentions of verification timing "NEVER" 2024-12-20 16:35:54 +01:00
Jones cf4713b2d8 Disable lombok generated annotations 2024-12-20 16:24:21 +01:00
Jones f82f0ddd87 Remove spotless license header check 2024-12-14 13:32:00 +01:00
Jones 7f3d244030 Release 2.1.19 (#420) 2024-12-06 16:58:52 +01:00
JonesandAkkariin 3db8230e9f Minecraft 1.21.4 Support (#408)
* Add missing server protocol versions

* Add 1.21.4 client protocol version

* Fix SetHeldItemPacket for 1.21.4

* Fix codestyle issue caused by the merge

* Fix CI build error

* 1.21.4-pre1 support (#415)

* Update 1.21.4 protocol version to pre-release 1

* Add 1.21.4 registry

* Optimize the size of the `codec_1_21_2.nbt` registry.

* Update entity/item/block id mapping for 1.21.4-pre1

* Update packet registry for 1.21.4-pre1

* Add `onGround` field for `VehicleMovePacket` decode on 1.21.4-pre1.

* Minecraft 1.21.4-pre3 Support

* Minecraft 1.21.4-rc3 Support

* Fix VehicleMovePacket being broken for 1.21.3-

* Update codec for 1.21.4(-rc3)

* Fix 1.21.4 codec and protocol version

---------

Co-authored-by: Akkariin <fallencrystal@outlook.com>
2024-12-04 20:03:36 +01:00
Jones 8295119016 Remove unused TeleportEntityPacket 2024-12-01 15:28:06 +01:00
Jones a0b14214ae Release 2.1.18 (#418) 2024-12-01 14:00:32 +01:00
Jones 3d96a32f81 Fix crash issue on 1.13-1.13.2
(Backported minor changes from 3.0.0)
2024-12-01 01:23:54 +01:00
Jones de67ab12c2 Downgrade mysql-connector-j to 9.0.0 2024-11-30 19:46:08 +01:00
Jones adef9d9ccb Release 2.1.17 (#417) 2024-11-26 17:06:20 +01:00
Jones 71b40af656 Bump mariadb-java-client from 3.4.1 to 3.5.1 2024-11-23 17:07:21 +01:00
Jones 3a79711a8b Bump mysql-connector-j from 9.0.0 to 9.1.0 2024-11-23 17:06:29 +01:00
Jones 3094f12f84 Bump Gradle from 8.11 to 8.11.1 2024-11-23 17:02:51 +01:00
Jones a74c3da8d4 Use version name instead of ID for logging 2024-11-23 15:13:48 +01:00
dependabot[bot] 427768fc43 Bump io.netty:netty-all from 4.1.114.Final to 4.1.115.Final (#412)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.114.Final to 4.1.115.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.114.Final...netty-4.1.115.Final)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 03:57:07 +01:00
dependabot[bot] b0d7dfb735 Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#413)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 03:56:53 +01:00
Jones ff9216c4cc Add spotless for codestyle (#410)
* Add spotless for codestyle

* Fix license header file

* Bump Gradle from 8.10.2 to 8.11
2024-11-11 17:52:52 +01:00
257 changed files with 4988 additions and 2544 deletions

No files matched your search

+1
View File
@@ -22,3 +22,4 @@
# Common build-tool wrapper scripts ('.cmd' versions are handled by 'Common.gitattributes')
gradlew text eol=lf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+13 -2
View File
@@ -1,11 +1,11 @@
Before you start contributing, please take a moment to review our contributing guidelines.
If you can't contribute but like the project, feel free to show your support by starring the project on GitHub or
joining the [Discord](https://jonesdev.xyz/discord).
joining the [Discord](https://sonar.top/discord).
## Contributing Guidelines:
1. Fork the repository and create a new branch for your contribution.
2. Follow the coding guidelines for your code.
2. Adhere to the Coding Guidelines and LLM Guidelines.
3. Test your code before committing and pushing it.
4. Use [semantic](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) commit messages for your commits.
@@ -17,6 +17,17 @@ joining the [Discord](https://jonesdev.xyz/discord).
- Write clear comments to explain complex parts of your code.
- Ensure your code is easy to read and maintain.
## LLM Guidelines
Keep the use of AI (including LLMs) for code generation to an absolute minimum. If possible, avoid using AI entirely due to potential complications regarding authorship, intellectual property, and copyright.
If AI was utilized during the development of a Pull Request, you must disclose its exact use in the PR description. This includes:
- The specific tool and model used (e.g., GPT 5.4, Claude 4.7 Sonnet, Gemini 3.1 Pro)
- The nature of its use (e.g., optimization)
- The exact prompts used, where relevant
Please note that submissions will be rejected if AI usage raises concerns regarding code quality, licensing, or integrity.
## Bug Reports:
1. Check if someone has already reported the bug.
-1
View File
@@ -1 +0,0 @@
patreon: jonesdev
+1 -1
View File
@@ -4,5 +4,5 @@ contact_links:
url: https://docs.jonesdev.xyz/
about: Need help configuring Sonar or have questions regarding the installation and usage?
- name: Discord
url: https://jonesdev.xyz/discord/
url: https://sonar.top/discord/
about: If you have any further questions, please feel free to join the Discord server and open a ticket.
+15 -4
View File
@@ -11,7 +11,7 @@
[![](https://www.codefactor.io/repository/github/jonesdevelopment/sonar/badge/main)](https://www.codefactor.io/repository/github/jonesdevelopment/sonar/overview/main)
[![](https://img.shields.io/github/v/release/jonesdevelopment/sonar)](https://github.com/jonesdevelopment/sonar/releases)
[![](https://img.shields.io/github/issues/jonesdevelopment/sonar)](https://github.com/jonesdevelopment/sonar/issues)
[![](https://img.shields.io/discord/923308209769426994.svg?logo=discord)](https://jonesdev.xyz/discord)
[![](https://img.shields.io/discord/923308209769426994.svg?logo=discord)](https://sonar.top/discord)
[![](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
<br>
<!-- Quick navigation -->
@@ -21,11 +21,18 @@
|
[Pull Requests](https://github.com/jonesdevelopment/sonar/pulls)
|
[Discord](https://jonesdev.xyz/discord)
[Discord](https://sonar.top/discord)
|
[License](https://github.com/jonesdevelopment/sonar/?tab=readme-ov-file#license)
|
[Documentation](https://docs.jonesdev.xyz/)
</div>
> ### Notice about Sonar 3.0
>
> If you want more robust protection, better performance, and awesome new features, check out Sonar 3.0 at https://sonar.top/!
> You can read more about what sets Sonar 3.0 apart from Sonar 2.0 [here](<https://sonar.top/docs/features>).
## Design and Goal
* Effective, lightweight, and easy-to-use
* No unnecessary features and clean code
@@ -46,12 +53,16 @@ Sonar also protects against spambot attacks since it queues the incoming connect
### False positives
Sonar is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol, which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check.
If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues/new/choose) or a ticket on the [Discord server](https://jonesdev.xyz/discord/).
If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues/new/choose) or a ticket on the [Discord server](https://sonar.top/discord/).
## Sponsors
Massive thanks to the sponsors of Sonar who help keep this project running:
If you are interested in sponsoring this project, please contact me via [Discord](https://sonar.top/discord/).
### Past Sponsors
<a href="https://github.com/Chumb3x"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/79834154?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
<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>
<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).
+1 -1
View File
@@ -13,7 +13,7 @@ If you discover a security vulnerability within Sonar, please follow these steps
## Contact
If you have any questions or concerns regarding the security of this project, please open a ticket on Discord or contact me via email at contact@jonesdev.xyz.
If you have any questions or concerns regarding the security of this project, please open a ticket on Discord or contact me via email at support@sonar.top.
## Acknowledgments
+36 -34
View File
@@ -21,42 +21,44 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@v3
with:
arguments: build-sonar
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: bukkit/build/libs/Sonar-Bukkit.jar
if-no-files-found: error
- name: Build with Gradle
run: ./gradlew build-sonar
- name: Upload BungeeCord Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: bungeecord/build/libs/Sonar-Bungee.jar
if-no-files-found: error
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v4
with:
name: Sonar-Bukkit
path: bukkit/build/libs/Sonar-Bukkit.jar
if-no-files-found: error
- name: Upload Velocity Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: velocity/build/libs/Sonar-Velocity.jar
if-no-files-found: error
- name: Upload Paper Artifact
uses: actions/upload-artifact@v4
with:
name: Sonar-Paper
path: paper/build/libs/Sonar-Paper.jar
if-no-files-found: error
- name: Upload BungeeCord Artifact
uses: actions/upload-artifact@v4
with:
name: Sonar-Bungee
path: bungeecord/build/libs/Sonar-Bungee.jar
if-no-files-found: error
- name: Upload Velocity Artifact
uses: actions/upload-artifact@v4
with:
name: Sonar-Velocity
path: velocity/build/libs/Sonar-Velocity.jar
if-no-files-found: error
+2 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) ${YEAR} Sonar Contributors
* Copyright (C) 2026 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,3 +14,4 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-3
View File
@@ -14,6 +14,3 @@ tasks {
archiveFileName = "sonar-api-${rootProject.version}.jar"
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -21,11 +21,11 @@ import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.antibot.AntiBot;
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.database.controller.VerifiedPlayerController;
import xyz.jonesdev.sonar.api.event.SonarEventManager;
import xyz.jonesdev.sonar.api.fallback.Fallback;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.api.notification.ActionBarNotificationHandler;
import xyz.jonesdev.sonar.api.notification.ChatNotificationHandler;
@@ -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,30 +119,67 @@ public interface Sonar {
@SuppressWarnings("unused")
void setChatNotificationHandler(final @NotNull ChatNotificationHandler notificationHandler);
/**
* Reloads the Sonar configuration.
*/
void reload();
@NotNull
default SonarVersion getVersion() {
/**
* Gets the current version of Sonar.
*
* @return The {@link SonarVersion} instance.
*/
@NotNull default SonarVersion getVersion() {
return SonarVersion.INSTANCE;
}
@NotNull
default SonarEventManager getEventManager() {
/**
* Gets the event manager for Sonar.
*
* @return The {@link SonarEventManager} instance.
*/
@NotNull default SonarEventManager getEventManager() {
return SonarEventManager.INSTANCE;
}
@NotNull
default AttackTracker getAttackTracker() {
/**
* Gets the attack tracker for Sonar.
*
* @return The {@link AttackTracker} instance.
*/
@NotNull default AttackTracker getAttackTracker() {
return AttackTracker.INSTANCE;
}
@NotNull
default Fallback getFallback() {
return Fallback.INSTANCE;
/**
* Gets the anti-bot handler for Sonar.
*
* @return The {@link AntiBot} instance.
*/
@NotNull default AntiBot getAntiBot() {
return AntiBot.INSTANCE;
}
@NotNull
static Sonar get() {
/**
* 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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -30,6 +30,8 @@ public enum SonarPlatform {
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder"),
BUNGEE("BungeeCord", 19109, "inbound-boss",
pipeline -> "packet-encoder"),
PAPER("Paper", 19110, "packet_handler",
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder"),
VELOCITY("Velocity", 19107, "handler",
pipeline -> "minecraft-encoder");
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback;
package xyz.jonesdev.sonar.api.antibot;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.AccessLevel;
@@ -23,9 +23,9 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.antibot.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.api.antibot.ratelimit.Ratelimiter;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
import java.net.InetAddress;
import java.util.concurrent.ConcurrentHashMap;
@@ -33,12 +33,12 @@ import java.util.concurrent.ConcurrentMap;
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class Fallback {
public static final Fallback INSTANCE = new Fallback();
public final class AntiBot {
public static final AntiBot INSTANCE = new AntiBot();
private final ConcurrentMap<InetAddress, Integer> online = new ConcurrentHashMap<>(128);
private final ConcurrentMap<InetAddress, Boolean> connected = new ConcurrentHashMap<>(256);
private final FallbackLoginQueue queue = new FallbackLoginQueue();
private final LoginQueue queue = new LoginQueue();
@Setter
private Cache<String, Integer> blacklist;
@Setter
@@ -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) {
public 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);
}
}
@@ -0,0 +1,31 @@
/*
* 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.api.antibot;
public interface ChannelPipelines {
String SONAR_INACTIVE_LISTENER = "sonar-inactive-listener";
String SONAR_INBOUND_HANDLER = "sonar-inbound-handler";
String SONAR_FRAME_DECODER = "sonar-frame-decoder";
String SONAR_FRAME_ENCODER = "sonar-frame-encoder";
String SONAR_TIMEOUT = "sonar-timeout";
String SONAR_PACKET_HANDLER = "sonar-packet-handler";
String SONAR_PACKET_ENCODER = "sonar-packet-encoder";
String SONAR_PACKET_DECODER = "sonar-packet-decoder";
String SONAR_TAIL_EXCEPTIONS = "sonar-exception-tail";
String SONAR_BANDWIDTH = "sonar-bandwidth-counter";
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback;
package xyz.jonesdev.sonar.api.antibot;
import lombok.AccessLevel;
import lombok.Getter;
@@ -30,7 +30,7 @@ import java.util.concurrent.Executors;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public final class FallbackLoginQueue {
public final class LoginQueue {
// Fixed thread pool executor for all new verifications
private static final int THREAD_POOL_SIZE = Math.min(0x7fff, Runtime.getRuntime().availableProcessors() * 2);
private static final ExecutorService QUEUE_EXECUTOR = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
@@ -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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* Copyright (C) 2026 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
@@ -15,35 +15,30 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback;
package xyz.jonesdev.sonar.api.antibot;
import io.netty.channel.Channel;
import io.netty.util.ReferenceCountUtil;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.antibot.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import java.net.InetAddress;
public interface FallbackUser {
@NotNull
Channel channel();
public interface SonarUser {
@NotNull Channel channel();
@NotNull
InetAddress getInetAddress();
@NotNull InetAddress getInetAddress();
@NotNull
ProtocolVersion getProtocolVersion();
@NotNull ProtocolVersion getProtocolVersion();
@NotNull
SystemTimer getLoginTimer();
@NotNull SystemTimer getLoginTimer();
@NotNull
String getFingerprint();
@NotNull String getFingerprint();
@NotNull
String getUsername();
@NotNull String getUsername();
boolean isForceCaptcha();
@@ -51,6 +46,8 @@ public interface FallbackUser {
boolean isGeyser();
@Nullable String getHostname();
/**
* Disconnect the player during/after verification
* using our custom Disconnect packet.
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback.captcha;
package xyz.jonesdev.sonar.api.antibot.captcha;
import org.jetbrains.annotations.NotNull;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback.protocol;
package xyz.jonesdev.sonar.api.antibot.protocol;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -26,52 +26,60 @@ import java.util.Map;
@Getter
@RequiredArgsConstructor
public enum ProtocolVersion {
UNKNOWN(-1),
MINECRAFT_1_7_2(4),
MINECRAFT_1_7_6(5),
MINECRAFT_1_8(47),
MINECRAFT_1_9(107),
MINECRAFT_1_9_1(108),
MINECRAFT_1_9_2(109),
MINECRAFT_1_9_4(110),
MINECRAFT_1_10(210),
MINECRAFT_1_11(315),
MINECRAFT_1_11_1(316),
MINECRAFT_1_12(335),
MINECRAFT_1_12_1(338),
MINECRAFT_1_12_2(340),
MINECRAFT_1_13(393),
MINECRAFT_1_13_1(401),
MINECRAFT_1_13_2(404),
MINECRAFT_1_14(477),
MINECRAFT_1_14_1(480),
MINECRAFT_1_14_2(485),
MINECRAFT_1_14_3(490),
MINECRAFT_1_14_4(498),
MINECRAFT_1_15(573),
MINECRAFT_1_15_1(575),
MINECRAFT_1_15_2(578),
MINECRAFT_1_16(735),
MINECRAFT_1_16_1(736),
MINECRAFT_1_16_2(751),
MINECRAFT_1_16_3(753),
MINECRAFT_1_16_4(754),
MINECRAFT_1_17(755),
MINECRAFT_1_17_1(756),
MINECRAFT_1_18(757),
MINECRAFT_1_18_2(758),
MINECRAFT_1_19(759),
MINECRAFT_1_19_1(760),
MINECRAFT_1_19_3(761),
MINECRAFT_1_19_4(762),
MINECRAFT_1_20(763),
MINECRAFT_1_20_2(764),
MINECRAFT_1_20_3(765),
MINECRAFT_1_20_5(766),
MINECRAFT_1_21(767),
MINECRAFT_1_21_2(768);
UNKNOWN(-1, "Unknown"),
MINECRAFT_1_7_2(4, "1.7.2"),
MINECRAFT_1_7_6(5, "1.7.6"),
MINECRAFT_1_8(47, "1.8"),
MINECRAFT_1_9(107, "1.9"),
MINECRAFT_1_9_1(108, "1.9.1"),
MINECRAFT_1_9_2(109, "1.9.2"),
MINECRAFT_1_9_4(110, "1.9.3"),
MINECRAFT_1_10(210, "1.10"),
MINECRAFT_1_11(315, "1.11"),
MINECRAFT_1_11_1(316, "1.11.1"),
MINECRAFT_1_12(335, "1.12"),
MINECRAFT_1_12_1(338, "1.12.1"),
MINECRAFT_1_12_2(340, "1.12.2"),
MINECRAFT_1_13(393, "1.13"),
MINECRAFT_1_13_1(401, "1.13.1"),
MINECRAFT_1_13_2(404, "1.13.2"),
MINECRAFT_1_14(477, "1.14"),
MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"),
MINECRAFT_1_14_3(490, "1.14.3"),
MINECRAFT_1_14_4(498, "1.14.4"),
MINECRAFT_1_15(573, "1.15"),
MINECRAFT_1_15_1(575, "1.15.1"),
MINECRAFT_1_15_2(578, "1.15.2"),
MINECRAFT_1_16(735, "1.16"),
MINECRAFT_1_16_1(736, "1.16.1"),
MINECRAFT_1_16_2(751, "1.16.2"),
MINECRAFT_1_16_3(753, "1.16.3"),
MINECRAFT_1_16_4(754, "1.16.4"),
MINECRAFT_1_17(755, "1.17"),
MINECRAFT_1_17_1(756, "1.17.1"),
MINECRAFT_1_18(757, "1.18"),
MINECRAFT_1_18_2(758, "1.18.2"),
MINECRAFT_1_19(759, "1.19"),
MINECRAFT_1_19_1(760, "1.19.1"),
MINECRAFT_1_19_3(761, "1.19.3"),
MINECRAFT_1_19_4(762, "1.19.4"),
MINECRAFT_1_20(763, "1.20"),
MINECRAFT_1_20_2(764, "1.20.2"),
MINECRAFT_1_20_3(765, "1.20.3"),
MINECRAFT_1_20_5(766, "1.20.5"),
MINECRAFT_1_21(767, "1.21"),
MINECRAFT_1_21_2(768, "1.21.2"),
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"),
MINECRAFT_1_21_9(773, "1.21.9"),
MINECRAFT_1_21_11(774, "1.21.11"),
MINECRAFT_26_1(775, "26.1");
private final int protocol;
private final String name;
public static final ProtocolVersion LATEST_VERSION;
public static final Map<Integer, ProtocolVersion> ID_TO_PROTOCOL_CONSTANT;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback.ratelimit;
package xyz.jonesdev.sonar.api.antibot.ratelimit;
import org.jetbrains.annotations.NotNull;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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());
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -30,6 +30,7 @@ import xyz.jonesdev.sonar.api.command.SonarCommand;
import xyz.jonesdev.sonar.api.database.ormlite.H2DatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MariaDbDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MysqlDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.PostgresDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.webhook.DiscordWebhook;
import java.io.File;
@@ -66,14 +67,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 +88,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 +113,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
@@ -139,7 +140,7 @@ public final class SonarConfiguration {
// General settings
logPlayerAddresses = generalConfig.getBoolean("general.log-player-addresses");
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), 0, 99);
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), -1, 99);
// Attack tracker
minPlayersForAttack = clamp(generalConfig.getInt("attack-tracker.min-players-for-attack"), 2, 1024);
@@ -158,28 +159,29 @@ 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");
verification.gravity.checkCollisions = generalConfig.getBoolean("verification.checks.collision.enabled");
verification.gravity.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.gravity.timing"));
verification.gravity.collisionTiming = Verification.Timing.valueOf(generalConfig.getString("verification.checks.collision.timing"));
verification.gravity.captchaOnFail = generalConfig.getBoolean("verification.checks.gravity.captcha-on-fail");
verification.gravity.maxMovementTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-movement-ticks"), 2, 100);
verification.vehicle.enabled = generalConfig.getBoolean("verification.checks.vehicle.enabled");
verification.vehicle.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.vehicle.timing"));
verification.vehicle.minimumPackets = clamp(generalConfig.getInt("verification.checks.vehicle.minimum-packets"), 0, 20);
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;
verification.map.placeInOffhand = generalConfig.getBoolean("verification.checks.map-captcha.place-in-offhand");
final String backgroundPath = generalConfig.getString("verification.checks.map-captcha.background");
if (!backgroundPath.isEmpty()) {
@@ -187,7 +189,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,14 +200,15 @@ 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.checkEagler = generalConfig.getBoolean("verification.check-eagler-players");
verification.logConnections = generalConfig.getBoolean("verification.log-connections");
verification.logDuringAttack = generalConfig.getBoolean("verification.log-during-attack");
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions");
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout"), 1000, 30000);
verification.writeTimeout = clamp(generalConfig.getInt("verification.write-timeout"), 1000, 30000);
verification.maxPacketCount = clamp(generalConfig.getInt("verification.max-packets"), 200, 9999);
verification.reconnectDelay = clamp(generalConfig.getInt("verification.rejoin-delay"), 0, 100000);
verification.rememberTime = clamp(generalConfig.getInt("verification.remember-time"), 0, 86400000);
verification.blacklistTime = clamp(generalConfig.getInt("verification.blacklist-time"), 0, 86400000);
@@ -220,16 +223,12 @@ public final class SonarConfiguration {
webhook.content = webhookConfig.getString("webhook.content");
webhook.footer.text = webhookConfig.getString("webhook.embed.footer.text");
webhook.footer.iconUrl = webhookConfig.getString("webhook.embed.footer.icon-url");
final String realEmbedPath = "webhook.embed";
final String embedPath = realEmbedPath + ".";
webhook.embed.title = webhookConfig.getString(embedPath + "title");
webhook.embed.titleUrl = webhookConfig.getString(embedPath + "title-url");
webhook.embed.description = String.join("\n", webhookConfig.getStringList(embedPath + "description"));
webhook.embed.r = webhookConfig.getInt(embedPath + "color.red");
webhook.embed.g = webhookConfig.getInt(embedPath + "color.green");
webhook.embed.b = webhookConfig.getInt(embedPath + "color.blue");
webhook.embed.title = webhookConfig.getString("webhook.embed.title");
webhook.embed.titleUrl = webhookConfig.getString("webhook.embed.title-url");
webhook.embed.description = String.join("\n", webhookConfig.getStringList("webhook.embed.description"));
webhook.embed.r = webhookConfig.getInt("webhook.embed.color.red");
webhook.embed.g = webhookConfig.getInt("webhook.embed.color.green");
webhook.embed.b = webhookConfig.getInt("webhook.embed.color.blue");
if (!webhook.url.isEmpty()) {
if (webhook.username.isEmpty()) {
@@ -254,7 +253,12 @@ public final class SonarConfiguration {
SonarCommand.prepareCachedTabSuggestions();
supportUrl = messagesConfig.getString("support-url");
supportUrl = messagesConfig.getString("support-url")
.replace("jonesdev.xyz", "sonar.top") // Migration: Replace legacy URL
.trim();
if (supportUrl.contains("://sonar.top/")) {
Sonar.get0().getLogger().warn("Consider entering a custom support URL (if applicable).");
}
header = MiniMessage.miniMessage().deserialize(
String.join("<newline>", messagesConfig.getStringList("header")),
Placeholder.unparsed("support-url", supportUrl),
@@ -292,7 +296,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;
@@ -301,7 +305,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;
}
@@ -376,19 +380,20 @@ public final class SonarConfiguration {
private int maxTries;
private String alphabet;
private File backgroundImage;
private boolean placeInOffhand;
}
@Getter
public static final class Gravity {
private boolean enabled;
private boolean checkCollisions;
private Timing timing;
private Timing collisionTiming;
private boolean captchaOnFail;
private int maxMovementTicks;
}
@Getter
public static final class Vehicle {
private boolean enabled;
private Timing timing;
private int minimumPackets;
}
@@ -418,14 +423,15 @@ public final class SonarConfiguration {
private int timeOfDay;
private boolean checkGeyser;
private boolean checkEagler;
private boolean logConnections;
private boolean logDuringAttack;
private boolean debugXYZPositions;
private Pattern validNameRegex;
private Pattern validLocaleRegex;
private int readTimeout;
private int writeTimeout;
private int maxPacketCount;
private int reconnectDelay;
private int rememberTime;
private int blacklistTime;
@@ -451,14 +457,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")
@@ -472,6 +478,13 @@ public final class SonarConfiguration {
.version("2.2.220")
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
.build()),
POSTGRESQL("PostgreSQL", "jdbc:postgresql://%s:%d/%s%s", new PostgresDatabaseTypeAdapter(),
Library.builder()
.groupId("org{}postgresql")
.artifactId("postgresql")
.version("42.7.8")
.relocate("org{}postgresql", "xyz{}jonesdev{}sonar{}libs{}postgresql")
.build()),
NONE("None", null, null, null);
private final String displayName;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -21,6 +21,7 @@ import com.alessiodp.libby.LibraryManager;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
@@ -54,19 +55,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,21 +82,23 @@ 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"),
cachedDatabaseType.getDatabaseType());
final String user = Sonar.get0().getConfig().getGeneralConfig().getString("database.username");
final String pass = Sonar.get0().getConfig().getGeneralConfig().getString("database.password");
connectionSource = cachedDatabaseType == SonarConfiguration.Database.Type.H2
? new JdbcConnectionSource(jdbcURL, user, pass, cachedDatabaseType.getDatabaseType())
: new JdbcPooledConnectionSource(jdbcURL, user, pass, cachedDatabaseType.getDatabaseType());
try {
TableUtils.createTableIfNotExists(connectionSource, VerifiedPlayer.class);
@@ -107,6 +112,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 +120,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 +139,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 +148,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 +165,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);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -0,0 +1,14 @@
package xyz.jonesdev.sonar.api.database.ormlite;
import com.j256.ormlite.jdbc.db.PostgresDatabaseType;
import org.jetbrains.annotations.NotNull;
public final class PostgresDatabaseTypeAdapter extends PostgresDatabaseType {
// We need to override the default driver class name
// to use the custom relocated Postgres driver
@Override
protected String @NotNull [] getDriverClassNames() {
return new String[]{"xyz.jonesdev.sonar.libs.postgresql.Driver"};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -20,8 +20,8 @@ package xyz.jonesdev.sonar.api.event.impl;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import xyz.jonesdev.sonar.api.antibot.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
@Getter
@Setter
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -20,12 +20,12 @@ package xyz.jonesdev.sonar.api.event.impl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.antibot.SonarUser;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@Getter
@ToString
@RequiredArgsConstructor
public final class UserBlacklistedEvent implements SonarEvent {
private final FallbackUser user;
private final SonarUser user;
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -21,13 +21,13 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.antibot.SonarUser;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@Getter
@ToString
@RequiredArgsConstructor
public final class UserVerifyFailedEvent implements SonarEvent {
private final @NotNull FallbackUser user;
private final @NotNull SonarUser user;
private final @NotNull String reason;
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -20,12 +20,12 @@ package xyz.jonesdev.sonar.api.event.impl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.antibot.SonarUser;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@Getter
@ToString
@RequiredArgsConstructor
public final class UserVerifyJoinEvent implements SonarEvent {
private final FallbackUser user;
private final SonarUser user;
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -20,12 +20,12 @@ package xyz.jonesdev.sonar.api.event.impl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.antibot.SonarUser;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
@Getter
@ToString
@RequiredArgsConstructor
public final class UserVerifySuccessEvent implements SonarEvent {
private final FallbackUser user;
private final SonarUser user;
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback;
public interface FallbackPipelines {
String FALLBACK_INACTIVE_LISTENER = "sonar-inactive-listener";
String FALLBACK_INBOUND_HANDLER = "sonar-inbound-handler";
String FALLBACK_FRAME_DECODER = "sonar-frame-decoder";
String FALLBACK_FRAME_ENCODER = "sonar-frame-encoder";
String FALLBACK_TIMEOUT = "sonar-timeout";
String FALLBACK_PACKET_HANDLER = "sonar-packet-handler";
String FALLBACK_PACKET_ENCODER = "sonar-packet-encoder";
String FALLBACK_PACKET_DECODER = "sonar-packet-decoder";
String FALLBACK_TAIL_EXCEPTIONS = "sonar-exception-tail";
String FALLBACK_BANDWIDTH = "sonar-bandwidth-counter";
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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().getAntiBot().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getAntiBot().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getAntiBot().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];
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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().getAntiBot().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getAntiBot().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getAntiBot().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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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().getAntiBot().getConnected().size();
final int queuedPlayers = Sonar.get0().getAntiBot().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().getAntiBot().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)
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -48,23 +48,24 @@ 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("Failed to check for updates (is your firewall set up properly?): {}",
throwable);
lastCheckResult = CheckResult.API_ERROR;
}
});
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
+20 -4
View File
@@ -3,18 +3,20 @@ import net.kyori.indra.git.IndraGitExtension
plugins {
java
alias(libs.plugins.shadow)
alias(libs.plugins.indra.git) apply true
alias(libs.plugins.indra.git)
alias(libs.plugins.spotless)
}
allprojects {
repositories {
mavenCentral()
maven(url = "https://repo.jonesdev.xyz/releases/") // Bungee & Velocity proxy module
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") // libby
maven(url = "https://repo.alessiodp.com/snapshots/") // libby
}
apply(plugin = "java")
apply(plugin = "com.gradleup.shadow")
apply(plugin = "com.diffplug.spotless")
dependencies {
compileOnly(rootProject.libs.lombok)
@@ -31,6 +33,16 @@ allprojects {
compileOnly(rootProject.libs.libby.core)
}
spotless {
java {
endWithNewline()
formatAnnotations()
removeUnusedImports()
trimTrailingWhitespace()
indentWithSpaces(2)
}
}
tasks {
shadowJar {
// Set the file name of the shadowed jar
@@ -50,6 +62,7 @@ allprojects {
relocate("org.mariadb", "xyz.jonesdev.sonar.libs.mariadb")
relocate("org.h2", "xyz.jonesdev.sonar.libs.h2")
relocate("com.jhlabs", "xyz.jonesdev.sonar.libs.jhlabs")
relocate("org.postgresql", "xyz.jonesdev.sonar.libs.postgresql")
// Exclude unnecessary metadata information
exclude("META-INF/*/**")
@@ -80,14 +93,17 @@ allprojects {
attributes["Git-Commit"] = gitCommit
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
}
}
tasks {
// This is a small wrapper tasks to simplify the building process
register("build-sonar") {
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "velocity")
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:shadowJar") }
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "paper", "velocity")
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:spotlessApply", "$it:shadowJar") }
dependsOn(buildTasks)
}
}
+1 -5
View File
@@ -8,7 +8,7 @@ bukkit {
name = rootProject.name
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
authors = listOf("Jones Development", "Sonar Contributors")
website = "https://jonesdev.xyz/discord/"
website = "https://sonar.top/discord/"
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
softDepend = listOf("Geyser-Spigot", "floodgate", "Protocolize", "ProtocolSupport",
"ViaVersion", "packetevents", "ProtocolLib", "FastLogin")
@@ -32,7 +32,6 @@ dependencies {
implementation(project(":common"))
compileOnly(rootProject.libs.spigot)
testCompileOnly(rootProject.libs.spigot)
implementation(rootProject.libs.adventure.platform.bukkit) {
exclude(module = "adventure-nbt")
@@ -46,6 +45,3 @@ tasks {
relocate("net.kyori", "xyz.jonesdev.sonar.libs.kyori")
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -28,8 +28,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.bukkit.antibot.BukkitInjector;
import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
import xyz.jonesdev.sonar.bukkit.listener.BukkitJoinListener;
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
@@ -108,8 +108,8 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand());
// Try to inject into the server
if (FallbackBukkitInjector.isLateBindEnabled()) {
getPlugin().getServer().getScheduler().runTask(getPlugin(), FallbackBukkitInjector::inject);
if (BukkitInjector.isLateBindEnabled()) {
getPlugin().getServer().getScheduler().runTask(getPlugin(), BukkitInjector::inject);
} else {
getPlugin().getServer().getPluginManager().registerEvents(new BukkitJoinListener(), getPlugin());
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -18,7 +18,7 @@
package xyz.jonesdev.sonar.bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
import xyz.jonesdev.sonar.bukkit.antibot.BukkitInjector;
public final class SonarBukkitPlugin extends JavaPlugin {
private SonarBukkit bootstrap;
@@ -26,8 +26,8 @@ public final class SonarBukkitPlugin extends JavaPlugin {
@Override
public void onLoad() {
// Inject early if late-bind is disabled
if (!FallbackBukkitInjector.isLateBindEnabled()) {
FallbackBukkitInjector.inject();
if (!BukkitInjector.isLateBindEnabled()) {
BukkitInjector.inject();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,37 +15,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bukkit.fallback;
package xyz.jonesdev.sonar.bukkit.antibot;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacket;
import xyz.jonesdev.sonar.api.antibot.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.InboundHandlerAdapter;
import xyz.jonesdev.sonar.common.protocol.SonarPacket;
import xyz.jonesdev.sonar.common.protocol.SonarPacketRegistry;
import xyz.jonesdev.sonar.common.protocol.packets.handshake.HandshakePacket;
import xyz.jonesdev.sonar.common.protocol.packets.login.LoginStartPacket;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.net.InetSocketAddress;
import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.*;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.SONAR_INBOUND_HANDLER;
import static xyz.jonesdev.sonar.common.protocol.packets.handshake.HandshakePacket.*;
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
final class BukkitInboundHandler extends InboundHandlerAdapter {
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
private FallbackPacketRegistry.ProtocolRegistry registry;
private SonarPacketRegistry.ProtocolRegistry registry;
FallbackBukkitInboundHandler() {
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
BukkitInboundHandler() {
updateRegistry(SonarPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
channelRemovalListener = (pipeline, name, handler) -> {
super.channelRemovalListener = (pipeline, name, handler) -> {
final ChannelInactiveListener inactiveListener = pipeline.get(ChannelInactiveListener.class);
if (inactiveListener != null) {
@@ -54,9 +54,9 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
};
}
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
public void updateRegistry(final @NotNull SonarPacketRegistry registry,
final @NotNull ProtocolVersion protocolVersion) {
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
this.registry = registry.getProtocolRegistry(SonarPacketRegistry.Direction.SERVERBOUND, protocolVersion);
}
@Override
@@ -79,7 +79,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
}
final int packetId = ProtocolUtil.readVarInt(byteBuf);
final FallbackPacket packet = registry.createPacket(packetId);
final SonarPacket packet = registry.createPacket(packetId);
// Skip the packet if it's not registered within Sonar's packet registry
if (packet == null) {
@@ -119,9 +119,10 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
// Let the actual handler know about the handshake packet
handleHandshake(ctx, handshake.getHostname(), handshake.getProtocolVersionId());
// Be ready for the next packet (which is supposed to be a login packet)
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
updateRegistry(SonarPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
default:
byteBuf.release();
throw ProtocolUtil.DEBUG ? new DecoderException("Bad handshake intent " + handshake.getIntent())
: QuietDecoderException.INSTANCE;
}
@@ -135,9 +136,9 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf.retain());
// TODO: recode this?
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
if (inboundHandler != null) {
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
final ChannelHandler inboundHandler = ctx.pipeline().remove(SONAR_INBOUND_HANDLER);
if (inboundHandler != null && channelRemovalListener != null) {
channelRemovalListener.accept(ctx.pipeline(), SONAR_INBOUND_HANDLER, inboundHandler);
}
}, loginStart.getUsername(), socketAddress);
byteBuf.release();
@@ -146,6 +147,8 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf);
} else {
ctx.fireChannelRead(msg);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bukkit.fallback;
package xyz.jonesdev.sonar.bukkit.antibot;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@@ -26,7 +26,7 @@ import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
import xyz.jonesdev.sonar.common.netty.SonarInjectedChannelInitializer;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.reflect.Field;
@@ -34,8 +34,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INACTIVE_LISTENER;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.SONAR_INACTIVE_LISTENER;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.SONAR_PACKET_DECODER;
import static xyz.jonesdev.sonar.bukkit.SonarBukkit.INITIALIZE_LISTENER;
// Check out these links if you want to see some more magic
@@ -43,7 +43,7 @@ import static xyz.jonesdev.sonar.bukkit.SonarBukkit.INITIALIZE_LISTENER;
// https://github.com/dmulloy2/ProtocolLib/blob/master/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java
@UtilityClass
public class FallbackBukkitInjector {
public class BukkitInjector {
private final String LEGACY_NMS_PACKAGE;
private final String OBC_PACKAGE;
@@ -253,10 +253,10 @@ public class FallbackBukkitInjector {
INITIALIZE_LISTENER.thenAccept(__ -> {
try {
childHandlerField.set(_bootstrap, new FallbackInjectedChannelInitializer(originalInitializer,
childHandlerField.set(_bootstrap, new SonarInjectedChannelInitializer(originalInitializer,
pipeline -> {
pipeline.addAfter("splitter", FALLBACK_PACKET_DECODER, new FallbackBukkitInboundHandler());
pipeline.addFirst(FALLBACK_INACTIVE_LISTENER, new ChannelInactiveListener());
pipeline.addAfter("splitter", SONAR_PACKET_DECODER, new BukkitInboundHandler());
pipeline.addFirst(SONAR_INACTIVE_LISTENER, new ChannelInactiveListener());
}
));
} catch (IllegalAccessException exception) {
@@ -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);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bukkit.fallback;
package xyz.jonesdev.sonar.bukkit.antibot;
import lombok.Getter;
@@ -70,7 +70,19 @@ public enum BukkitServerVersion {
MINECRAFT_1_20_4,
MINECRAFT_1_20_5,
MINECRAFT_1_20_6,
MINECRAFT_1_21;
MINECRAFT_1_21,
MINECRAFT_1_21_1,
MINECRAFT_1_21_2,
MINECRAFT_1_21_3,
MINECRAFT_1_21_4,
MINECRAFT_1_21_5,
MINECRAFT_1_21_6,
MINECRAFT_1_21_7,
MINECRAFT_1_21_8,
MINECRAFT_1_21_9,
MINECRAFT_1_21_10,
MINECRAFT_1_21_11,
MINECRAFT_26_1;
static final BukkitServerVersion[] REVERSED_VALUES;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bukkit.fallback;
package xyz.jonesdev.sonar.bukkit.antibot;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -24,7 +24,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
import xyz.jonesdev.sonar.bukkit.antibot.BukkitInjector;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import java.lang.invoke.MethodHandle;
@@ -32,7 +32,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.SONAR_BANDWIDTH;
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/JoinListener.java
public final class BukkitJoinListener implements Listener {
@@ -46,13 +46,13 @@ public final class BukkitJoinListener implements Listener {
static {
try {
final Method handleMethod = FallbackBukkitInjector.getOBCClass("entity.CraftPlayer")
final Method handleMethod = BukkitInjector.getOBCClass("entity.CraftPlayer")
.getDeclaredMethod("getHandle");
final Field listenerField = FallbackBukkitInjector.findField(false, handleMethod.getReturnType(),
final Field listenerField = BukkitInjector.findField(false, handleMethod.getReturnType(),
"PlayerConnection", "ServerGamePacketListenerImpl");
final Field networkManagerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
final Field networkManagerField = BukkitInjector.findField(true, listenerField.getType(),
"NetworkManager", "Connection");
final Field channelField = FallbackBukkitInjector.getFieldAt(networkManagerField.getType(), Channel.class, 0);
final Field channelField = BukkitInjector.getFieldAt(networkManagerField.getType(), Channel.class, 0);
_handleMethod = MethodHandles.lookup().unreflect(handleMethod);
_listenerField = MethodHandles.lookup().unreflectGetter(listenerField);
@@ -88,11 +88,11 @@ public final class BukkitJoinListener implements Listener {
}
// Close the channel if Sonar's handlers are not found when the PlayerJoinEvent is called
if (channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
if (channel.pipeline().context(SONAR_BANDWIDTH) == null) {
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);
}
}
}
-4
View File
@@ -14,7 +14,6 @@ dependencies {
implementation(project(":common"))
compileOnly(rootProject.libs.bungeecord)
testCompileOnly(rootProject.libs.bungeecord)
implementation(rootProject.libs.adventure.platform.bungee) {
exclude(module = "adventure-nbt")
@@ -30,6 +29,3 @@ tasks {
archiveFileName = "Sonar-Bungee.jar" // Hardcode a shortened version of the name
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -28,8 +28,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.bungee.antibot.BungeeInjector;
import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand;
import xyz.jonesdev.sonar.bungee.fallback.FallbackBungeeInjector;
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
import java.util.UUID;
@@ -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());
@@ -103,7 +107,7 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
// Make sure to inject into the server's connection handler
FallbackBungeeInjector.inject();
BungeeInjector.inject();
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bungee.fallback;
package xyz.jonesdev.sonar.bungee.antibot;
import io.netty.channel.ChannelHandlerContext;
import net.md_5.bungee.netty.ChannelWrapper;
@@ -25,16 +25,16 @@ import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LoginRequest;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
import xyz.jonesdev.sonar.common.InboundHandlerAdapter;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.net.InetSocketAddress;
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.STATUS;
import static xyz.jonesdev.sonar.common.protocol.packets.handshake.HandshakePacket.STATUS;
final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
final class BungeeInboundHandler extends InboundHandlerAdapter {
private static final MethodHandle CHANNEL_WRAPPER_GETTER;
@@ -0,0 +1,96 @@
/*
* 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.bungee.antibot;
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.netty.SonarInjectedChannelInitializer;
import java.lang.reflect.Field;
import static net.md_5.bungee.netty.PipelineUtils.PACKET_DECODER;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.SONAR_PACKET_HANDLER;
@UtilityClass
public class BungeeInjector {
public void inject() {
try {
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);
injectOnOldBungeeCord(childField);
} catch (Exception 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 SonarInjectedChannelInitializer(
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, SONAR_PACKET_HANDLER,
new BungeeInboundHandler()));
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;
}
SonarInjectedChannelInitializer.inject(channel,
pipeline -> pipeline.addAfter(PACKET_DECODER, SONAR_PACKET_HANDLER,
new BungeeInboundHandler()));
return true;
});
ProxyServer.getInstance().unsafe().setFrontendChannelInitializer(newInitializer);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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);
@@ -1,59 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import lombok.experimental.UtilityClass;
import net.md_5.bungee.netty.PipelineUtils;
import sun.misc.Unsafe;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
import java.lang.reflect.Field;
import static net.md_5.bungee.netty.PipelineUtils.PACKET_DECODER;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_HANDLER;
@UtilityClass
public class FallbackBungeeInjector {
public void inject() {
try {
final Field childField = PipelineUtils.class.getField("SERVER_CHILD");
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);
} catch (Exception exception) {
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
}
}
}
+2 -5
View File
@@ -2,9 +2,6 @@ dependencies {
compileOnly(project(":api"))
compileOnly(rootProject.libs.imagefilters)
testCompileOnly(project(":api"))
testCompileOnly(rootProject.libs.imagefilters)
testImplementation(project(":api"))
testImplementation(rootProject.libs.imagefilters)
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -23,7 +23,7 @@ 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.api.antibot.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.filters.CurvesOverlayFilter;
import xyz.jonesdev.sonar.captcha.filters.NoiseOverlayFilter;
@@ -49,8 +49,11 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
private static final Random RANDOM = new Random();
private static final Color[] COLORS = new Color[4];
private static final float[] COLOR_FRACTIONS = new float[COLORS.length];
static final Font FONT;
static {
FONT = TTFFontProvider.loadFont("/assets/fonts/Kingthings_Trypewriter_2.ttf");
FBM.setAmount(0.6f);
FBM.setScale(15);
@@ -109,11 +112,11 @@ 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,
graphics.setPaint(new RadialGradientPaint(0, 0, width,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
}
@@ -124,8 +127,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
for (int i = 0; i < answer.length; i++) {
// Create a glyph vector for the character with a random font
final Font font = StandardTTFFontProvider.FONTS[RANDOM.nextInt(StandardTTFFontProvider.FONTS.length)];
glyphs[i] = font.createGlyphVector(ctx, String.valueOf(answer[i]));
glyphs[i] = FONT.createGlyphVector(ctx, String.valueOf(answer[i]));
}
final double scalingXY = 5 - Math.min(answer.length, 5) * 0.65;
@@ -135,7 +137,7 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
.mapToDouble(glyph -> glyph.getLogicalBounds().getWidth() * scalingXY - 1)
.sum();
double beginX = Math.max(Math.min(width / 2D - totalWidth / 2D, totalWidth), 0);
double beginY = (height + StandardTTFFontProvider.STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
double beginY = (height + TTFFontProvider.STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
// Draw each glyph one by one
for (final GlyphVector glyph : glyphs) {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -17,33 +17,23 @@
package xyz.jonesdev.sonar.captcha;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.InputStream;
import java.util.Objects;
@UtilityClass
class StandardTTFFontProvider {
private static final String[] FONT_NAMES = {"Kingthings_Trypewriter_2"};
static final Font[] FONTS = new Font[FONT_NAMES.length];
public final class TTFFontProvider {
static final int STANDARD_FONT_SIZE = 25;
static {
for (int i = 0; i < FONT_NAMES.length; i++) {
FONTS[i] = loadFont(String.format("/assets/fonts/%s.ttf", FONT_NAMES[i]));
}
}
private static Font loadFont(final @NotNull String path) {
public static Font loadFont(final @NotNull String path) {
try (final InputStream inputStream = StandardCaptchaGenerator.class.getResourceAsStream(path)) {
// Load the font from the TTF file
final Font customFont = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(inputStream));
// Set the font size and style
return customFont.deriveFont(Font.PLAIN, STANDARD_FONT_SIZE);
} catch (Exception exception) {
throw new IllegalStateException(exception);
throw new IllegalStateException("Error loading font. Does your environment support fonts?", exception);
}
}
}
@@ -0,0 +1,210 @@
/*
* 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.complex;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.antibot.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.TTFFontProvider;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
@RequiredArgsConstructor
public final class ComplexCaptchaGenerator implements CaptchaGenerator {
private final File background;
private final int width = 128, height = 128;
private BufferedImage backgroundImage;
private static final Font[] FONTS = {
TTFFontProvider.loadFont("/assets/fonts/dotness.ttf"), // by http://bythebutterfly.com/
TTFFontProvider.loadFont("/assets/fonts/DotMatrix.ttf"),
};
private static final Random RANDOM = new SecureRandom();
private static final double MAX_ROTATION_RADIANS = Math.toRadians(15);
private static final int LINE_COUNT = 8;
private static final int MIN_LINE_COLOR = 100;
private static final int MAX_TEXT_COLOR = 150;
private static final int DOT_COUNT = 100;
private @NotNull BufferedImage createBackgroundImage() {
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
final Graphics2D graphics = image.createGraphics();
if (background == null) {
graphics.setBackground(Color.WHITE);
graphics.clearRect(0, 0, image.getWidth(), image.getHeight());
return image;
}
if (backgroundImage == null) {
try {
backgroundImage = ImageIO.read(background);
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
graphics.drawImage(backgroundImage, 0, 0, Color.WHITE, null);
return image;
}
@Override
public @NotNull BufferedImage createImage(char @NotNull [] answer) {
final BufferedImage image = createBackgroundImage();
final Graphics2D graphics = image.createGraphics();
final String text = new String(answer);
final int padding = 12;
final double maxWidth = width - 2D * padding;
final double maxHeight = height - 2D * padding;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
final FontRenderContext frc = graphics.getFontRenderContext();
float optimalSize = 24;
final float trialSize = 100;
final Rectangle2D trialBounds = FONTS[0].deriveFont(trialSize).getStringBounds(text, frc);
if (trialBounds.getWidth() > 0 && trialBounds.getHeight() > 0) {
final double scaleFactorWidth = maxWidth / trialBounds.getWidth();
final double scaleFactorHeight = maxHeight / trialBounds.getHeight();
optimalSize = (float) (trialSize * Math.min(scaleFactorWidth, scaleFactorHeight));
}
if (optimalSize < 1f) {
optimalSize = 1f;
}
final Font finalFont = FONTS[0].deriveFont(optimalSize);
graphics.setFont(finalFont);
final Rectangle2D fullBounds = finalFont.getStringBounds(text, frc);
double currentX = (width - fullBounds.getWidth()) / 2D;
final double baselineY = (height / 2D) - (fullBounds.getHeight() / 2D) - fullBounds.getY();
double currentY = baselineY;
for (final char c : answer) {
final Font font = FONTS[RANDOM.nextInt(FONTS.length)].deriveFont(optimalSize);
graphics.setFont(font);
final String character = String.valueOf(c);
final Rectangle2D charBounds = font.getStringBounds(character, frc);
final double charWidth = charBounds.getWidth();
final double charHeight = charBounds.getHeight();
final double charCenterX = currentX + charWidth / 2D;
final double charCenterY = baselineY + charBounds.getY() + charHeight / 2D;
final Color[] colors = new Color[2 + RANDOM.nextInt(2)];
final float[] colorFractions = new float[colors.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = getRandomTextColor();
colorFractions[i] = i / (float) colors.length;
}
graphics.setPaint(new RadialGradientPaint((float) currentX, (float) currentY, (float) charWidth,
colorFractions, colors, MultipleGradientPaint.CycleMethod.REFLECT));
final double rotation = (RANDOM.nextDouble() * 2 * MAX_ROTATION_RADIANS) - MAX_ROTATION_RADIANS;
final AffineTransform originalTransform = graphics.getTransform();
graphics.rotate(rotation, charCenterX, charCenterY);
final TextLayout layout = new TextLayout(character, font, frc);
final AffineTransform translate = AffineTransform.getTranslateInstance(currentX, currentY);
final Shape outline = layout.getOutline(translate);
graphics.fill(outline);
graphics.setTransform(originalTransform);
currentX += charWidth;
currentY += RANDOM.nextDouble(charHeight / 4) - charHeight / 8;
}
drawNoiseLines(graphics);
drawNoiseDots(graphics);
graphics.dispose();
return image;
}
private @NotNull Color getRandomTextColor() {
final int r = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
final int g = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
final int b = Math.min(RANDOM.nextInt(256), MAX_TEXT_COLOR);
return new Color(r, g, b, 220);
}
private @NotNull Color getRandomColor() {
final int r = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int g = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int b = Math.max(RANDOM.nextInt(256), MIN_LINE_COLOR);
final int a = Math.max(Math.min(RANDOM.nextInt(256), 200), MIN_LINE_COLOR);
return new Color(r, g, b, a);
}
private void drawNoiseLines(final @NotNull Graphics2D graphics) {
for (int i = 0; i < LINE_COUNT; i++) {
graphics.setColor(getRandomColor());
graphics.setStroke(new BasicStroke(1f + RANDOM.nextFloat() * 2f,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
if (RANDOM.nextBoolean()) {
float x1 = RANDOM.nextInt(width);
float y1 = RANDOM.nextInt(height);
float ctrlX = RANDOM.nextInt(width);
float ctrlY = RANDOM.nextInt(height);
float x2 = RANDOM.nextInt(width);
float y2 = RANDOM.nextInt(height);
graphics.draw(new QuadCurve2D.Float(x1, y1, ctrlX, ctrlY, x2, y2));
} else {
int x1 = RANDOM.nextInt(width);
int y1 = RANDOM.nextInt(height);
int x2 = RANDOM.nextInt(width);
int y2 = RANDOM.nextInt(height);
graphics.drawLine(x1, y1, x2, y2);
}
}
}
private void drawNoiseDots(final @NotNull Graphics2D graphics) {
for (int i = 0; i < DOT_COUNT; i++) {
graphics.setColor(getRandomColor());
final int x = RANDOM.nextInt(width);
final int y = RANDOM.nextInt(height);
final int radius = 1 + RANDOM.nextInt(5);
graphics.fillOval(x, y, radius, radius);
}
}
}
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class CircleInverseFilter {
private final int amount, minRadius, maxRadiusExpansion;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image) {
final int minX = image.getWidth() / 4;
final int minY = image.getHeight() / 4;
for (int i = 0; i < amount; i++) {
// Generate random position and radius for the circle
final float centerX = minX + RANDOM.nextInt(image.getWidth() - minX * 2);
final float centerY = minY + RANDOM.nextInt(image.getHeight() - minY * 2);
final int radius = minRadius + RANDOM.nextInt(maxRadiusExpansion);
final Circle circle = new Circle(centerX, centerY, radius);
// Draw inverted circle
_addInvertedCircle(image, circle);
}
}
private static void _addInvertedCircle(final @NotNull BufferedImage bufferedImage,
final @NotNull Circle circle) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
if (isWithinCircle(x, y, circle, circle.radius)) {
// Invert colors within the circle
final int rgb = bufferedImage.getRGB(x, y);
final int invertedRGB = invertColorAndFilter(rgb);
bufferedImage.setRGB(x, y, invertedRGB);
}
}
}
}
private static boolean isWithinCircle(final float x, final float y, final @NotNull Circle circle, int radius) {
final float distanceX = x - circle.centerX;
final float distanceY = y - circle.centerY;
return distanceX * distanceX + distanceY * distanceY <= radius * radius;
}
@Getter
@RequiredArgsConstructor
static final class Circle {
private final float centerX;
private final float centerY;
private final int radius;
}
private static int invertColorAndFilter(final int rgb) {
final Color color = new Color(rgb);
final int red = Math.max(Color.LIGHT_GRAY.getRed() - color.getRed(), 0);
final int green = Math.max(Color.LIGHT_GRAY.getGreen() - color.getGreen(), 0);
final int blue = Math.max(Color.LIGHT_GRAY.getBlue() - color.getBlue(), 0);
return new Color(red, green, blue).getRGB();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class GridOverlayFilter {
private final int amount, lineWidth, randomOffset;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image,
final @NotNull Graphics2D graphics) {
final int spacingX = image.getWidth() / amount + lineWidth;
final int spacingY = image.getHeight() / amount + lineWidth;
int currentX = spacingX / 2;
int currentY = spacingY / 2;
for (int i = 0; i < amount; i++) {
graphics.setStroke(new BasicStroke(lineWidth * 0.5f + lineWidth * RANDOM.nextFloat()));
graphics.drawLine(currentX, 0, currentX + RANDOM.nextInt(randomOffset), image.getHeight());
graphics.drawLine(0, currentY, image.getWidth(), currentY + RANDOM.nextInt(randomOffset));
currentX += spacingX + lineWidth;
currentY += spacingY + lineWidth;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -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.antibot.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;
}
}
Binary file not shown.
@@ -0,0 +1,5 @@
My fonts are free for personal use. You also MAY redistribute the fonts on your website as long as you guve credit to http://bythebutterfly.com as the creator of the fonts. If you are interested in commercial use, please email me at BYTHEBUTTERFLY@GMAIL.COM
Thanks, and enjoy :)
Vanessa
@@ -0,0 +1,12 @@
Kingthings EULA - (End User License Agreement).
All the fonts currently published on my website (www.kingthingsfonts.co.uk) are free for you to use for any purpose you wish. Please do not change my original font files (.TTF).
There are some paid-for versions of my fonts (with much enhanced character sets) available from to buy from www.cheapprofonts.com, these contain their own EULA and are not included or implied in this notice.
Please enjoy all your creative work - and if you come up with anything you like, I would appreciate a picture.
Regards
Kevin King
Exeter, England - February 2009
+4 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -16,7 +16,8 @@
*/
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.api.antibot.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.complex.ComplexCaptchaGenerator;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@@ -28,7 +29,7 @@ import java.util.Random;
public class Main {
public void main(final String... args) throws IOException {
final Random random = new Random();
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(null);
final CaptchaGenerator standardCaptchaGenerator = new ComplexCaptchaGenerator(null);
final long start = System.currentTimeMillis();
final char[] dictionary = {'a', 'b', 'c', 'd', 'e', 'f'/*, 'g'*/, 'h'/*, 'i'*/, 'j',
-3
View File
@@ -3,6 +3,3 @@ dependencies {
implementation(project(":captcha"))
compileOnly(rootProject.libs.adventure.nbt)
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback;
package xyz.jonesdev.sonar.common;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
@@ -29,8 +29,8 @@ import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
@ChannelHandler.Sharable
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class FallbackBandwidthHandler extends ChannelDuplexHandler {
public static final ChannelHandler INSTANCE = new FallbackBandwidthHandler();
public final class BandwidthHandler extends ChannelDuplexHandler {
public static final ChannelHandler INSTANCE = new BandwidthHandler();
@Override
public void channelRead(final ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback;
package xyz.jonesdev.sonar.common;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -25,13 +25,14 @@ import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.netty.TailExceptionsHandler;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import java.net.InetAddress;
import java.util.function.Consumer;
@RequiredArgsConstructor
public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
public final class InboundHandler extends ChannelInboundHandlerAdapter {
private final Consumer<ChannelPipeline> sonarPipelineInjector;
@Setter
@Getter
@@ -55,11 +56,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().getAntiBot().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().getAntiBot().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 +68,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().getAntiBot().getOnline().compute(inetAddress,
(ignored1, count) -> count == null || count <= 1 ? null : count - 1);
}
return null;
@@ -85,5 +86,8 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx, final Throwable cause) throws Exception {
ctx.close();
if (TailExceptionsHandler.LOG_EXCEPTIONS) {
cause.printStackTrace(System.err);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback;
package xyz.jonesdev.sonar.common;
import io.netty.channel.*;
import lombok.AllArgsConstructor;
@@ -23,13 +23,14 @@ import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.antibot.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.fingerprint.FingerprintingUtil;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTimeoutHandler;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.netty.SonarTimeoutHandler;
import xyz.jonesdev.sonar.common.protocol.SonarPacket;
import xyz.jonesdev.sonar.common.protocol.SonarPacketEncoder;
import xyz.jonesdev.sonar.common.protocol.SonarPacketRegistry;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.EaglerUtil;
import xyz.jonesdev.sonar.common.util.GeyserUtil;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
@@ -39,15 +40,16 @@ import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.*;
import static xyz.jonesdev.sonar.common.protocol.SonarPacketPreparer.*;
@AllArgsConstructor
@RequiredArgsConstructor
public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
public abstract class InboundHandlerAdapter extends ChannelInboundHandlerAdapter {
protected @Nullable String username;
protected ProtocolVersion protocolVersion;
protected RemovalListener channelRemovalListener = RemovalListener.EMPTY;
protected @Nullable String handshakeHostname;
protected @Nullable RemovalListener channelRemovalListener;
/**
* Validates and handles incoming handshake packets
@@ -64,7 +66,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
protocolVersion = ProtocolVersion.fromId(protocol);
ctx.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
handshakeHostname = hostname;
ctx.pipeline().addFirst(SONAR_BANDWIDTH, BandwidthHandler.INSTANCE);
}
/**
@@ -94,25 +97,25 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
this.username = username;
final InetAddress inetAddress = socketAddress.getAddress();
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
ctx.pipeline().get(InboundHandler.class).setInetAddress(inetAddress);
// Check if Fallback is already verifying a player with the same IP address
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
// Check if Sonar is already verifying a player with the same IP address
if (Sonar.get0().getAntiBot().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().getAntiBot().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
@@ -120,27 +123,34 @@ 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().getAntiBot().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;
}
// Completely skip EaglercraftX connections if configured
if (EaglerUtil.isEaglerConnection(ctx.channel())
&& !Sonar.get0().getConfig().getVerification().isCheckEagler()) {
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().getAntiBot().getRatelimiter().attempt(inetAddress)) {
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -149,7 +159,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().getAntiBot().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);
@@ -158,7 +168,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
}
// Create an instance for the user and let the verification handler take over the channel
return () -> new FallbackUserWrapper(ctx, inetAddress, protocolVersion, username, fingerprint, geyser);
return () -> new UserWrapper(ctx, inetAddress, protocolVersion, username, fingerprint, geyser,
handshakeHostname);
});
}
@@ -168,51 +179,47 @@ 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;
}
final int maxOnlinePerIp = Sonar.get0().getConfig().getMaxOnlinePerIp();
if (maxOnlinePerIp > 0) {
final int newCount = Sonar.get0().getAntiBot().getOnline().compute(inetAddress,
(__, count) -> count == null ? 1 : count + 1);
if (newCount > maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return;
}
// 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;
});
}
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")
// Don't remove floodgate's pipelines
|| entry.getKey().startsWith("geyser")
|| entry.getKey().startsWith("floodgate")
|| entry.getKey().startsWith("eagler")
|| entry.getKey().equals("DefaultChannelPipeline$TailContext#0")) {
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(),
ctx.pipeline().addFirst(SONAR_TIMEOUT, new SonarTimeoutHandler(
Sonar.get0().getConfig().getVerification().getReadTimeout(),
Sonar.get0().getConfig().getVerification().getWriteTimeout(),
TimeUnit.MILLISECONDS));
}
private static void customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull SonarPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
if (channel.isActive()) {
if (channel.eventLoop().inEventLoop()) {
@@ -227,23 +234,23 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
* Disconnect the player before verification (during login)
*/
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final @NotNull SonarPacket packet,
final @NotNull ProtocolVersion protocolVersion) {
// TODO: recode this
// Remove the connection handler pipeline to completely take over the channel
final String handler = Sonar.get().getPlatform().getConnectionHandler();
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) {
// We don't need to update the encoder if it has already been replaced by Sonar
if (!(currentEncoder instanceof FallbackPacketEncoder)) {
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
newEncoder.updateRegistry(FallbackPacketRegistry.LOGIN);
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
if (!(currentEncoder instanceof SonarPacketEncoder)) {
final SonarPacketEncoder newEncoder = new SonarPacketEncoder(protocolVersion);
newEncoder.updateRegistry(SonarPacketRegistry.LOGIN);
channel.pipeline().replace(encoder, SONAR_PACKET_ENCODER, newEncoder);
}
ProtocolUtil.closeWith(channel, protocolVersion, packet);
} else {
@@ -256,7 +263,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) -> {};
}
}
@@ -0,0 +1,148 @@
/*
* 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.common;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.antibot.SonarUser;
import xyz.jonesdev.sonar.api.antibot.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.netty.MinecraftVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.netty.MinecraftVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.netty.TailExceptionsHandler;
import xyz.jonesdev.sonar.common.protocol.SonarPacketDecoder;
import xyz.jonesdev.sonar.common.protocol.SonarPacketEncoder;
import xyz.jonesdev.sonar.common.protocol.SonarPacketPreparer;
import xyz.jonesdev.sonar.common.protocol.SonarPacketRegistry;
import xyz.jonesdev.sonar.common.protocol.packets.play.DisconnectPacket;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import xyz.jonesdev.sonar.common.verification.LoginHandler;
import java.net.InetAddress;
import static xyz.jonesdev.sonar.api.antibot.ChannelPipelines.*;
@Getter
@ToString(of = {"protocolVersion", "inetAddress", "hostname", "geyser"})
public final class UserWrapper implements SonarUser {
@Accessors(fluent = true)
private final Channel channel;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final String fingerprint;
private final String username;
private final boolean geyser;
private final @Nullable String hostname;
private final SystemTimer loginTimer;
@Setter
private boolean forceCaptcha;
public UserWrapper(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull String username,
final @NotNull String fingerprint,
final boolean geyser,
final @Nullable String hostname) {
this.channel = ctx.channel();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
this.fingerprint = fingerprint;
this.geyser = geyser;
this.hostname = hostname;
this.loginTimer = new SystemTimer();
GlobalSonarStatistics.totalAttemptedVerifications++;
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.get0().getConfig().formatAddress(inetAddress))
.replace("<protocol>", protocolVersion.getName()));
}
// Call the VerifyJoinEvent for external API usage
Sonar.get0().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
// Make sure the channel is still active
if (!channel.isActive()) {
return;
}
// How? Is there some kind of de-sync or race condition?
if (channel.pipeline().context(SONAR_FRAME_ENCODER) != null) {
channel.close(); // Nope ¯\_(ツ)_/¯
return;
}
// Mark the player as connected by caching them in a map of verifying players
Sonar.get0().getAntiBot().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
final SonarPacketEncoder newEncoder = new SonarPacketEncoder(protocolVersion);
channel.pipeline().addFirst(SONAR_FRAME_ENCODER, MinecraftVarIntLengthEncoder.INSTANCE);
channel.pipeline().addLast(SONAR_PACKET_ENCODER, newEncoder);
// Replace normal decoder to allow custom packets
final SonarPacketDecoder newDecoder = new SonarPacketDecoder(protocolVersion);
channel.pipeline().addFirst(SONAR_FRAME_DECODER, new MinecraftVarInt21FrameDecoder());
channel.pipeline().addLast(SONAR_PACKET_DECODER, newDecoder);
// We're sending the LoginSuccess packet now
newDecoder.updateRegistry(SonarPacketRegistry.LOGIN);
newEncoder.updateRegistry(SonarPacketRegistry.LOGIN);
// Send LoginSuccess packet to make the client think they are joining the server
write(SonarPacketPreparer.loginSuccess);
// pre-1.20.2 clients do not have the configuration stage
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
newDecoder.updateRegistry(SonarPacketRegistry.GAME);
newEncoder.updateRegistry(SonarPacketRegistry.GAME);
}
// Listen for all incoming packets by setting the packet listener
newDecoder.setListener(new LoginHandler(this));
// Make sure to catch all exceptions during the verification
channel.pipeline().addLast(SONAR_TAIL_EXCEPTIONS, TailExceptionsHandler.INSTANCE);
});
}
@Override
public void disconnect(final @NotNull Component reason) {
final SonarPacketEncoder encoder = channel.pipeline().get(SonarPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() == SonarPacketRegistry.LOGIN;
ProtocolUtil.closeWith(channel, protocolVersion, new DisconnectPacket(reason, duringLogin));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -47,7 +47,7 @@ class LibraryLoader {
Library.builder()
.groupId("com{}github{}ben-manes{}caffeine")
.artifactId("caffeine")
.version("3.1.8")
.version("3.2.3")
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
.build(),
// ORMLite
@@ -68,26 +68,38 @@ class LibraryLoader {
// Only load adventure if not on Velocity
if (platform != SonarPlatform.VELOCITY) {
libraryManager.loadLibraries(
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-minimessage")
.version("4.17.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-serializer-gson")
.version("4.17.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-nbt")
.version("4.17.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build()
);
if (platform != SonarPlatform.PAPER) {
libraryManager.loadLibraries(
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-minimessage")
.version("4.21.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-serializer-gson")
.version("4.21.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-nbt")
.version("4.21.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build()
);
} else {
// On Paper, we only relocate the nbt package since the rest of adventure is bundled with the server.
libraryManager.loadLibrary(
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-nbt")
.version("4.21.0")
.relocate("net{}kyori{}adventure{}nbt", "xyz{}jonesdev{}sonar{}libs{}kyori{}adventure{}nbt")
.build()
);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Sonar Contributors
* 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
@@ -36,9 +36,9 @@ import xyz.jonesdev.sonar.api.notification.ActionBarNotificationHandler;
import xyz.jonesdev.sonar.api.notification.ChatNotificationHandler;
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.ratelimit.CaffeineCacheRatelimiter;
import xyz.jonesdev.sonar.common.fallback.ratelimit.NoopCacheRatelimiter;
import xyz.jonesdev.sonar.common.protocol.SonarPacketPreparer;
import xyz.jonesdev.sonar.common.ratelimit.CaffeineCacheRatelimiter;
import xyz.jonesdev.sonar.common.ratelimit.NoopCacheRatelimiter;
import xyz.jonesdev.sonar.common.service.ScheduledServiceManager;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.subcommand.*;
@@ -137,26 +137,26 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Prepare cached packets
getLogger().info("Taking cached snapshots of all packets...");
FallbackPreparer.prepare();
SonarPacketPreparer.prepare();
// Update ratelimiter cache
getFallback().setRatelimiter(getConfig().getVerification().getReconnectDelay() > 0L
getAntiBot().setRatelimiter(getConfig().getVerification().getReconnectDelay() > 0L
? new CaffeineCacheRatelimiter(Duration.ofMillis(getConfig().getVerification().getReconnectDelay()))
: NoopCacheRatelimiter.INSTANCE);
// Update blacklist cache
final long blacklistTime = getConfig().getVerification().getBlacklistTime();
final boolean blacklistExists = getFallback().getBlacklist() != null;
final boolean blacklistExists = getAntiBot().getBlacklist() != null;
// Make sure the blacklist is only set when we need it to prevent data loss
if (!blacklistExists // Make sure we create a new cache if it doesn't exist yet
|| getFallback().getBlacklistTime() != blacklistTime) {
|| getAntiBot().getBlacklistTime() != blacklistTime) {
// Create a new cache with the configured blacklist time
getFallback().setBlacklist(Caffeine.newBuilder()
getAntiBot().setBlacklist(Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMillis(getConfig().getVerification().getBlacklistTime()))
.ticker(Ticker.systemTicker())
.build());
// Store the new blacklist time, so we don't have to reset the blacklist every reload
getFallback().setBlacklistTime(blacklistTime);
getAntiBot().setBlacklistTime(blacklistTime);
// Warn the user about changing the expiry of the blacklist values
if (blacklistExists) {
getLogger().warn("The blacklist has been reset as the duration of entries has changed.");
@@ -1,141 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTailExceptionsHandler;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.DisconnectPacket;
import xyz.jonesdev.sonar.common.fallback.verification.FallbackPreJoinHandler;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.net.InetAddress;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
@Getter
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
public final class FallbackUserWrapper implements FallbackUser {
@Accessors(fluent = true)
private final Channel channel;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final String fingerprint;
private final String username;
private final boolean geyser;
private final SystemTimer loginTimer;
@Setter
private boolean forceCaptcha;
public FallbackUserWrapper(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull String username,
final @NotNull String fingerprint,
final boolean geyser) {
this.channel = ctx.channel();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
this.fingerprint = fingerprint;
this.geyser = geyser;
this.loginTimer = new SystemTimer();
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")
.replace("<username>", username)
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
.replace("<protocol>", String.valueOf(protocolVersion.getProtocol())));
}
// Call the VerifyJoinEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
// Make sure the channel is still active
if (!channel.isActive()) {
return;
}
// Mark the player as connected by caching them in a map of verifying players
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
// TODO: recode injection
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
channel.pipeline().addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Replace normal decoder to allow custom packets
// TODO: recode injection
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
channel.pipeline().addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
channel.pipeline().addLast(FALLBACK_PACKET_DECODER, newDecoder);
// We're sending the LoginSuccess packet now
newDecoder.updateRegistry(FallbackPacketRegistry.LOGIN);
newEncoder.updateRegistry(FallbackPacketRegistry.LOGIN);
// Send LoginSuccess packet to make the client think they are joining the server
write(FallbackPreparer.loginSuccess);
// pre-1.20.2 clients do not have the configuration stage
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
newDecoder.updateRegistry(FallbackPacketRegistry.GAME);
newEncoder.updateRegistry(FallbackPacketRegistry.GAME);
}
// Listen for all incoming packets by setting the packet listener
newDecoder.setListener(new FallbackPreJoinHandler(this));
// Make sure to catch all exceptions during the verification
// TODO: recode injection
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
});
}
@Override
public void disconnect(final @NotNull Component reason) {
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() == FallbackPacketRegistry.LOGIN;
ProtocolUtil.closeWith(channel, protocolVersion, new DisconnectPacket(reason, duringLogin));
}
}
@@ -1,283 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.IntBinaryTag;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationStartEvent;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import xyz.jonesdev.sonar.common.fallback.protocol.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.util.Random;
import java.util.UUID;
@UtilityClass
public class FallbackPreparer {
private final Random RANDOM = new Random();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
public static final BlockType[] POSSIBLE_BLOCK_TYPES = BlockType.values();
public final FallbackPacket[] BLOCKS_PACKETS = new FallbackPacket[POSSIBLE_BLOCK_TYPES.length];
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (vanilla protocol maximum)
public final int SPAWN_X_POSITION = 16 / 2; // middle of the chunk
public final int SPAWN_Z_POSITION = 16 / 2; // middle of the chunk
public final int PLATFORM_Y_POSITION = 1 + RANDOM.nextInt(255); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 3000 + RANDOM.nextInt(500); // Random high altitude
public final int FIRST_TELEPORT_ID = RANDOM.nextInt();
public final int SECOND_TELEPORT_ID = RANDOM.nextInt();
public final int PRE_JOIN_KEEP_ALIVE_ID = RANDOM.nextInt();
private final int MAP_SLOT = RANDOM.nextInt(9);
public static final FallbackPacket CAPTCHA_SET_CONTAINER_SLOT = new SetContainerSlotPacket(
0, 36 + MAP_SLOT, 1, ItemType.FILLED_MAP, CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map id
.build());
public final static FallbackPacket CAPTCHA_HELD_ITEM_SLOT = new SetHeldItemPacket(MAP_SLOT);
public final FallbackPacket DEFAULT_ABILITIES = new PlayerAbilitiesPacket(0x00, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES = new PlayerAbilitiesPacket(0x02, 0, 0);
public final FallbackPacket NO_MOVE_ABILITIES_BEDROCK = new PlayerAbilitiesPacket(0x06, 0, 0);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, 10000, SPAWN_Z_POSITION, 0, 90, 0, 0, false, false, true));
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket();
public final FallbackPacket PRE_JOIN_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(PRE_JOIN_KEEP_ALIVE_ID));
public final FallbackPacket[] REGISTRY_SYNC_1_20 = new FallbackPacket[] {
new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null))};
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
public final FallbackPacket[] REGISTRY_SYNC_1_21_2 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_2);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
public static final FallbackPacket SPAWN_BOAT_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.BOAT, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket SPAWN_MINECART_ENTITY = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_ENTITY_ID, EntityType.MINECART, SPAWN_X_POSITION, IN_AIR_Y_POSITION - 16, SPAWN_Z_POSITION,
0, 0, 0, 0));
public static final FallbackPacket REMOVE_VEHICLE = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
public static final FallbackPacket SET_VEHICLE_PASSENGERS = new FallbackPacketSnapshot(
new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
public FallbackPacket loginSuccess;
public FallbackPacket welcomeMessage;
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
public static FallbackPacket joinGame;
public FallbackPacket defaultSpawnPosition;
public FallbackPacket spawnPosition;
public FallbackPacket fallStartPosition;
public FallbackPacket fallStartPositionLegacy;
public static FallbackPacket transferToOrigin;
public FallbackPacket blacklisted;
public FallbackPacket alreadyQueued;
public FallbackPacket alreadyVerifying;
public FallbackPacket reconnectedTooFast;
public FallbackPacket protocolBlacklisted;
public FallbackPacket unsupportedVersion;
public FallbackPacket tooManyOnlinePerIP;
public FallbackPacket[] xpCountdown;
public FallbackPacket updateTime;
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...");
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");
if (username.length() > 16) {
username = username.substring(0, 16);
}
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(uuid, username, true));
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(),
-1, 0, 0,
RANDOM.nextInt(3), 1, 0, 0,
new String[]{"lol"}, "lol", "flat",
DimensionType.OVERWORLD, RANDOM.nextLong() & 1337,
false, true, false,
false, false, false, true));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
double motionY = 0, fallDistance = 0;
for (int i = 0; i < maxMovementTick; i++) {
motionY = (motionY - 0.1) * 0.98f;
fallDistance += Math.abs(motionY);
}
// Set the dynamic block and collide Y position based on the maximum fall distance
dynamicSpawnYPosition = PLATFORM_Y_POSITION + (int) Math.ceil(fallDistance);
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, 0, FIRST_TELEPORT_ID, 0, false, false, true));
fallStartPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition - IN_AIR_Y_POSITION, SPAWN_Z_POSITION,
0, -90, SECOND_TELEPORT_ID, 1 << 1, false, false, true));
fallStartPositionLegacy = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
0, -90, 0, 0, false, false, true));
// Prepare collision platform positions
final int length = BLOCKS_PER_ROW / 2;
for (int i = 0; i < BLOCKS_PACKETS.length; i++) {
final BlockUpdate[] blocks = new BlockUpdate[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
int index = 0;
for (int x = 0; x < BLOCKS_PER_ROW; x++) {
for (int z = 0; z < BLOCKS_PER_ROW; z++) {
final BlockUpdate.BlockPosition position = new BlockUpdate.BlockPosition(
x + length, PLATFORM_Y_POSITION, z + length, 0, 0);
blocks[index++] = new BlockUpdate(position, POSSIBLE_BLOCK_TYPES[i]);
}
}
BLOCKS_PACKETS[i] = new FallbackPacketSnapshot(new UpdateSectionBlocksPacket(0, 0, blocks));
}
// 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));
// Prepare transfer packet
if (Sonar.get().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")));
} else {
transferToOrigin = null;
}
// Prepare update time packet
final int timeOfDay = Sonar.get().getConfig().getVerification().getTimeOfDay();
if (timeOfDay != 1000) {
updateTime = new UpdateTimePacket(0L, timeOfDay, false);
} else {
updateTime = null;
}
// If the welcome message is empty, we don't need to send a message to the player
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
if (welcome.isEmpty()) {
welcomeMessage = null;
} else {
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(welcome,
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
// 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())))));
// Prepare countdown
xpCountdown = new FallbackPacket[Sonar.get().getConfig().getVerification().getMap().getMaxDuration() / 1000];
for (int i = 0; i < xpCountdown.length; i++) {
final float bar = (float) i / xpCountdown.length;
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, 0));
}
// Update the CAPTCHA generator if necessary
final CaptchaGenerationStartEvent generationStartEvent = new CaptchaGenerationStartEvent(
Sonar.get().getFallback().getCaptchaGenerator());
Sonar.get().getEventManager().publish(generationStartEvent);
if (generationStartEvent.getCaptchaGenerator() == null
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
} else {
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare CAPTCHA answers
CaptchaPreparer.prepare();
} else {
// Throw away if not needed
enterCodeMessage = null;
incorrectCaptcha = null;
xpCountdown = null;
Sonar.get().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()
+ 150 /* some arbitrary leeway */;
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
return REGISTRY_SYNC_1_21_2;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21)) {
return REGISTRY_SYNC_1_21;
}
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_20_5)) {
return REGISTRY_SYNC_1_20_5;
}
return REGISTRY_SYNC_1_20;
}
}
Loaded 100 of 257 files, more files were not shown because too many files have changed in this diff. Show more