Compare commits

...
76 Commits
Author SHA1 Message Date
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
198 changed files with 1883 additions and 708 deletions

No files matched your search

+4
View File
@@ -53,6 +53,10 @@ Massive thanks to the sponsors of Sonar who help keep this project running:
<a href="https://github.com/Hydoxl"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/107579333?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
### Past Sponsors
<a href="https://github.com/ItzErpandX"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/84748484?v=4?s=400?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d" alt="logo" align="center"></a>
## Building
If you want to build your own version of Sonar, please take a look at the [Sonar building documentation](https://docs.jonesdev.xyz/development/building).
<br>
+29 -34
View File
@@ -21,42 +21,37 @@ 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 17
uses: actions/setup-java@v3
with:
java-version: '17'
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 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
+1 -1
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
@@ -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
@@ -49,22 +49,68 @@ public interface Sonar {
@ApiStatus.Internal
@NotNull Audience sender(final @NotNull Object object);
/**
* Gets the platform Sonar is running on (e.g., Bukkit, BungeeCord, Velocity).
*
* @return The {@link SonarPlatform} enum representing the current platform.
*/
@NotNull SonarPlatform getPlatform();
/**
* Gets the logger instance for Sonar.
*
* @return The {@link LoggerWrapper} instance.
*/
@NotNull LoggerWrapper getLogger();
/**
* Gets the Sonar configuration.
*
* @return The {@link SonarConfiguration} instance.
*/
@NotNull SonarConfiguration getConfig();
/**
* Gets the subcommand registry for Sonar commands.
*
* @return The {@link SubcommandRegistry} instance.
*/
@NotNull SubcommandRegistry getSubcommandRegistry();
/**
* Gets the controller for verified players.
* Used for managing players who have passed the verification.
*
* @return The {@link VerifiedPlayerController} instance.
*/
VerifiedPlayerController getVerifiedPlayerController();
/**
* Gets the handler for action bar notifications.
*
* @return The {@link ActionBarNotificationHandler} instance.
*/
@NotNull ActionBarNotificationHandler getActionBarNotificationHandler();
/**
* Gets the handler for chat notifications.
*
* @return The {@link ChatNotificationHandler} instance.
*/
@NotNull ChatNotificationHandler getChatNotificationHandler();
/**
* Gets the system timer used for tracking plugin launch time.
*
* @return The {@link SystemTimer} instance.
*/
@NotNull SystemTimer getLaunchTimer();
/**
* Gets the statistics tracker for Sonar.
*
* @return The {@link SonarStatistics} instance.
*/
@NotNull SonarStatistics getStatistics();
@SuppressWarnings("unused")
@@ -73,25 +119,67 @@ public interface Sonar {
@SuppressWarnings("unused")
void setChatNotificationHandler(final @NotNull ChatNotificationHandler notificationHandler);
/**
* Reloads the Sonar configuration.
*/
void reload();
/**
* Gets the current version of Sonar.
*
* @return The {@link SonarVersion} instance.
*/
@NotNull default SonarVersion getVersion() {
return SonarVersion.INSTANCE;
}
/**
* Gets the event manager for Sonar.
*
* @return The {@link SonarEventManager} instance.
*/
@NotNull default SonarEventManager getEventManager() {
return SonarEventManager.INSTANCE;
}
/**
* Gets the attack tracker for Sonar.
*
* @return The {@link AttackTracker} instance.
*/
@NotNull default AttackTracker getAttackTracker() {
return AttackTracker.INSTANCE;
}
/**
* Gets the anti-bot handler (called "Fallback") for Sonar.
*
* @return The {@link Fallback} instance.
*/
@NotNull default Fallback getFallback() {
return Fallback.INSTANCE;
}
/**
* Gets the instance of the Sonar API.
*
* @return The {@link Sonar} instance.
* @throws IllegalStateException If Sonar has not been initialized.
*/
@NotNull static Sonar get() {
return SonarSupplier.get();
}
/**
* Gets the instance of the Sonar API.
* Only use this method if you understand the implications,
* and have a specific reason to bypass the standard {@link #get()} method.
* In most cases, {@link #get()} is the preferred way to access the Sonar API.
*
* @return The Sonar instance.
*/
@ApiStatus.Internal
@NotNull static Sonar get0() {
return SonarSupplier.sonar;
}
}
@@ -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
@@ -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
@@ -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
@@ -66,14 +66,14 @@ public final class SonarConfiguration {
final URL defaultLanguageFile = Sonar.class.getResource("/assets/language.properties");
// Make sure the file actually exists before trying to copy it
if (defaultLanguageFile == null) {
Sonar.get().getLogger().error("Cannot check for custom language (is the file missing?)");
Sonar.get0().getLogger().error("Cannot check for custom language (is the file missing?)");
return DEFAULT_FALLBACK_LANGUAGE;
}
// Copy the file to the plugin data directory
try (final InputStream inputStream = defaultLanguageFile.openStream()) {
Files.copy(inputStream, languageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException exception) {
Sonar.get().getLogger().error("Error copying file: {}", exception);
Sonar.get0().getLogger().error("Error copying file: {}", exception);
return DEFAULT_FALLBACK_LANGUAGE;
}
}
@@ -87,13 +87,13 @@ public final class SonarConfiguration {
// Try parsing the property as a language
return Language.fromCode(property);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not find requested language: {}", throwable);
Sonar.get().getLogger().error("You can view a full list of valid language codes here:");
Sonar.get().getLogger().error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
Sonar.get().getLogger().error("If a translation does not exist yet, Sonar will use English (en).");
Sonar.get0().getLogger().error("Could not find requested language: {}", throwable);
Sonar.get0().getLogger().error("You can view a full list of valid language codes here:");
Sonar.get0().getLogger().error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
Sonar.get0().getLogger().error("If a translation does not exist yet, Sonar will use English (en).");
}
} catch (IOException exception) {
Sonar.get().getLogger().error("Error reading language file: {}", exception);
Sonar.get0().getLogger().error("Error reading language file: {}", exception);
}
return DEFAULT_FALLBACK_LANGUAGE;
}
@@ -112,13 +112,13 @@ public final class SonarConfiguration {
final String property = System.getProperty("user.language", "en");
language = Language.fromCode(property);
// Make sure the user knows that we're using the system language for translations
Sonar.get().getLogger().info("Using system language ({}) for translations.", language);
Sonar.get0().getLogger().info("Using system language ({}) for translations.", language);
} catch (Exception exception) {
Sonar.get().getLogger().warn("Could not use system language for translations.");
Sonar.get().getLogger().warn("Using default language ({}) for translations.", language);
Sonar.get0().getLogger().warn("Could not use system language for translations.");
Sonar.get0().getLogger().warn("Using default language ({}) for translations.", language);
}
} else {
Sonar.get().getLogger().info("Using custom language ({}) for translations.", language);
Sonar.get0().getLogger().info("Using custom language ({}) for translations.", language);
}
// Load all configurations
@@ -158,12 +158,12 @@ public final class SonarConfiguration {
verification.timing = Verification.Timing.valueOf(generalConfig.getString("verification.timing"));
// Warn the user if the verification timing is set to NEVER
if (verification.timing == Verification.Timing.NEVER) {
Sonar.get().getLogger().warn(" ");
Sonar.get().getLogger().warn("You have set the verification timing to 'NEVER'.");
Sonar.get().getLogger().warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
Sonar.get().getLogger().warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
Sonar.get().getLogger().warn("Please only edit this option if you really know what you are doing.");
Sonar.get().getLogger().warn(" ");
Sonar.get0().getLogger().warn(" ");
Sonar.get0().getLogger().warn("You have set the verification timing to 'NEVER'.");
Sonar.get0().getLogger().warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
Sonar.get0().getLogger().warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
Sonar.get0().getLogger().warn("Please only edit this option if you really know what you are doing.");
Sonar.get0().getLogger().warn(" ");
}
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled");
@@ -187,7 +187,7 @@ public final class SonarConfiguration {
if (backgroundFile.exists()) {
verification.map.backgroundImage = backgroundFile;
} else {
Sonar.get().getLogger().error("Could not find background image {}", backgroundFile.getAbsolutePath());
Sonar.get0().getLogger().error("Could not find background image {}", backgroundFile.getAbsolutePath());
}
}
@@ -288,7 +288,7 @@ public final class SonarConfiguration {
final String resourceName = url + "/" + language.getCode() + ".yml";
URL result = Sonar.class.getResource("/assets/" + resourceName);
if (result == null) {
Sonar.get().getLogger().warn("Could not find " + resourceName + "! Using en.yml!");
Sonar.get0().getLogger().warn("Could not find " + resourceName + "! Using en.yml!");
result = Objects.requireNonNull(Sonar.class.getResource("/assets/" + url + "/en.yml"));
}
return result;
@@ -297,7 +297,7 @@ public final class SonarConfiguration {
private static int clamp(final int v, final int max, final int min) {
final int output = Math.max(Math.min(v, min), max);
if (output != v) {
Sonar.get().getLogger().warn("Clamped configuration value {} to {}", v, output);
Sonar.get0().getLogger().warn("Clamped configuration value {} to {}", v, output);
}
return output;
}
@@ -447,18 +447,18 @@ 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")
.version("3.5.1")
.version("3.4.1")
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
.build()),
H2("H2", "jdbc:h2:file:%s", new H2DatabaseTypeAdapter(),
@@ -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
@@ -54,19 +54,21 @@ public final class VerifiedPlayerController {
@Getter
private final @NotNull SonarConfiguration.Database.Type cachedDatabaseType;
private final ExecutorService updateService = Executors.newSingleThreadExecutor();
@Getter
private int maximumAge;
public VerifiedPlayerController(final @NotNull LibraryManager libraryManager) {
final SonarConfiguration.Database database = Sonar.get().getConfig().getDatabase();
final SonarConfiguration.Database database = Sonar.get0().getConfig().getDatabase();
cachedDatabaseType = database.getType();
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
Sonar.get().getLogger().warn("Configure a database to save verified players.");
Sonar.get0().getLogger().warn("Configure a database to save verified players.");
return;
}
// Make sure to only load the driver once per database type
if (!cachedDatabaseType.isLoaded()) {
Sonar.get().getLogger().info("Loading {} driver version {}",
Sonar.get0().getLogger().info("Loading {} driver version {}",
cachedDatabaseType.getDatabaseType().getDatabaseName(),
cachedDatabaseType.getDatabaseDriver().getVersion());
libraryManager.loadLibrary(cachedDatabaseType.getDatabaseDriver());
@@ -79,20 +81,21 @@ public final class VerifiedPlayerController {
// H2 has a different JDBC URL layout
// https://www.codejava.net/java-se/jdbc/connect-to-h2-database-examples
if (cachedDatabaseType == SonarConfiguration.Database.Type.H2) {
final File file = new File(Sonar.get().getConfig().getPluginFolder(),
Sonar.get().getConfig().getGeneralConfig().getString("database.filename"));
final File file = new File(Sonar.get0().getConfig().getPluginFolder(),
Sonar.get0().getConfig().getGeneralConfig().getString("database.filename"));
jdbcURL = String.format(cachedDatabaseType.getConnectionString(), file.getAbsolutePath());
} else {
// Normal JDBC URL layout for MySQL/MariaDB/...
jdbcURL = String.format(cachedDatabaseType.getConnectionString(),
Sonar.get().getConfig().getGeneralConfig().getString("database.host"),
Sonar.get().getConfig().getGeneralConfig().getInt("database.port"),
Sonar.get().getConfig().getGeneralConfig().getString("database.name"));
Sonar.get0().getConfig().getGeneralConfig().getString("database.host"),
Sonar.get0().getConfig().getGeneralConfig().getInt("database.port"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.name"),
"?autoReconnect=true");
}
connectionSource = new JdbcConnectionSource(jdbcURL,
Sonar.get().getConfig().getGeneralConfig().getString("database.username"),
Sonar.get().getConfig().getGeneralConfig().getString("database.password"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.username"),
Sonar.get0().getConfig().getGeneralConfig().getString("database.password"),
cachedDatabaseType.getDatabaseType());
try {
@@ -107,6 +110,7 @@ public final class VerifiedPlayerController {
dao = DaoManager.createDao(connectionSource, VerifiedPlayer.class);
queryBuilder = dao.queryBuilder();
maximumAge = database.getMaximumAge();
// Make sure to run the clean task and the caching task in the same thread
// https://github.com/jonesdevelopment/sonar/issues/150
@@ -114,7 +118,7 @@ public final class VerifiedPlayerController {
if (connectionSource != null) {
try {
// Make sure to clear all outdated entries first
clearOld(database.getMaximumAge());
clearOld(maximumAge, false);
// Add all entries from the database to the cache
dao.queryForAll().forEach(verifiedPlayer -> cache.add(verifiedPlayer.getFingerprint()));
} catch (SQLException exception) {
@@ -133,6 +137,8 @@ public final class VerifiedPlayerController {
public void close() {
// The connection source will always be null if the database type is NONE.
if (connectionSource != null) {
// Shut down the update service before letting it re-use the connection.
updateService.shutdown();
try {
connectionSource.close();
} catch (Exception exception) {
@@ -140,14 +146,14 @@ public final class VerifiedPlayerController {
}
// Make sure not to reuse a closed connection
connectionSource = null;
updateService.shutdown();
}
}
/**
* Clear all old entries using the given timestamp
*/
private void clearOld(final @Range(from = 1, to = 365) int maximumAge) throws SQLException {
public void clearOld(final @Range(from = 1, to = 365) int maximumAge,
final boolean removeExisting) throws SQLException {
final long timestamp = Instant.now().minus(maximumAge, ChronoUnit.DAYS).getEpochSecond() * 1000L;
final List<VerifiedPlayer> oldEntries = queryBuilder.where()
@@ -157,8 +163,11 @@ public final class VerifiedPlayerController {
if (oldEntries != null && !oldEntries.isEmpty()) {
for (final VerifiedPlayer player : oldEntries) {
dao.delete(player);
if (removeExisting) {
cache.remove(player.getFingerprint());
}
}
Sonar.get().getLogger().info("Removed {} database entries older than {} days.",
Sonar.get0().getLogger().info("Removed {} database entries older than {} days.",
oldEntries.size(), maximumAge);
}
}
@@ -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
@@ -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
@@ -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
@@ -49,16 +49,16 @@ public final class Fallback {
private Ratelimiter<InetAddress> ratelimiter;
public boolean shouldVerifyNewPlayers() {
return shouldPerform(Sonar.get().getConfig().getVerification().getTiming());
return shouldPerform(Sonar.get0().getConfig().getVerification().getTiming());
}
public boolean shouldPerformCaptcha() {
return shouldPerform(Sonar.get().getConfig().getVerification().getMap().getTiming());
return shouldPerform(Sonar.get0().getConfig().getVerification().getMap().getTiming());
}
private static boolean shouldPerform(final SonarConfiguration.Verification.Timing timing) {
return timing == SonarConfiguration.Verification.Timing.ALWAYS
|| (timing == SonarConfiguration.Verification.Timing.DURING_ATTACK
&& Sonar.get().getAttackTracker().getCurrentAttack() != null);
&& Sonar.get0().getAttackTracker().getCurrentAttack() != null);
}
}
@@ -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 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) 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
@@ -70,7 +70,8 @@ public enum ProtocolVersion {
MINECRAFT_1_20_5(766, "1.20.5"),
MINECRAFT_1_21(767, "1.21"),
MINECRAFT_1_21_2(768, "1.21.2"),
MINECRAFT_1_21_4(769, "1.21.4");
MINECRAFT_1_21_4(769, "1.21.4"),
MINECRAFT_1_21_5(770, "1.21.5");
private final int protocol;
private final String name;
@@ -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
@@ -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,29 @@ 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("queued", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get0().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get0().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -74,7 +74,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 +83,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().getFallback().getQueue().getPlayers().size())),
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getConnected().size())),
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get0().getFallback().getBlacklist().estimatedSize())),
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalPlayersJoined())),
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getLoginsPerSecond())),
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getConnectionsPerSecond())),
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get0().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get0().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get0().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get0().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -74,8 +74,8 @@ public final class ChatNotificationHandler extends NotificationHandler {
}
// Send the title and chat messages to all online players
for (final UUID subscriber : Sonar.get().getChatNotificationHandler().getSubscribers()) {
final Audience audience = Sonar.get().audience(subscriber);
for (final UUID subscriber : Sonar.get0().getChatNotificationHandler().getSubscribers()) {
final Audience audience = Sonar.get0().audience(subscriber);
if (audience == null) continue;
// Send the message to all online subscribers
@@ -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().getFallback().getConnected().size();
final int queuedPlayers = Sonar.get0().getFallback().getQueue().getPlayers().size();
final int minPlayers = Sonar.get0().getConfig().getMinPlayersForAttack();
if (joinsPerSecond > minPlayers // Check the number of bots/joins per second.
|| verifyingPlayers > minPlayers // Check the number of verifying players.
@@ -66,10 +66,10 @@ public final class AttackTracker {
// An attack has been detected
if (currentAttack == null) {
currentAttack = new AttackStatistics();
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().getCache().size();
currentAttack.failedVerifications = Sonar.get().getStatistics().getTotalFailedVerifications();
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
Sonar.get().getChatNotificationHandler().handleNotification();
currentAttack.successfulVerifications = Sonar.get0().getVerifiedPlayerController().getCache().size();
currentAttack.failedVerifications = Sonar.get0().getStatistics().getTotalFailedVerifications();
Sonar.get0().getEventManager().publish(new AttackDetectedEvent());
Sonar.get0().getChatNotificationHandler().handleNotification();
} else {
// Reset attack timer if we're still under attack
currentAttack.timer.reset();
@@ -80,7 +80,7 @@ public final class AttackTracker {
// Update joins per second peak if necessary
currentAttack.peakJoinsPerSecond = joinsPerSecond;
}
final long connectionsPerSecond = Sonar.get().getStatistics().getConnectionsPerSecond();
final long connectionsPerSecond = Sonar.get0().getStatistics().getConnectionsPerSecond();
if (connectionsPerSecond > currentAttack.peakConnectionsPerSecond) {
// Update connections per second peak if necessary
currentAttack.peakConnectionsPerSecond = connectionsPerSecond;
@@ -96,14 +96,14 @@ public final class AttackTracker {
currentAttack.peakProcessMemoryUsage = processMemoryUsage;
}
} else if (currentAttack != null) {
if (currentAttack.duration.delay() > Sonar.get().getConfig().getMinAttackDuration()
&& currentAttack.timer.delay() > Sonar.get().getConfig().getAttackCooldownDelay()) {
if (currentAttack.duration.delay() > Sonar.get0().getConfig().getMinAttackDuration()
&& currentAttack.timer.delay() > Sonar.get0().getConfig().getAttackCooldownDelay()) {
// The current attack has stopped
Sonar.get().getEventManager().publish(new AttackMitigatedEvent(currentAttack));
Sonar.get0().getEventManager().publish(new AttackMitigatedEvent(currentAttack));
if (++attackThreshold > Sonar.get().getConfig().getMinAttackThreshold()) {
if (++attackThreshold > Sonar.get0().getConfig().getMinAttackThreshold()) {
// Post webhook to Discord
Optional.ofNullable(Sonar.get().getConfig().getWebhook().getDiscordWebhook())
Optional.ofNullable(Sonar.get0().getConfig().getWebhook().getDiscordWebhook())
.ifPresent(webhook -> {
// Save attack statistics
final long deltaInMillis = currentAttack.duration.delay();
@@ -116,15 +116,15 @@ public final class AttackTracker {
final String formattedDuration = String.format("%d minutes, %.0f seconds", minutes, seconds);
final String startTimestamp = String.valueOf(currentAttack.duration.getStart() / 1000L);
final String endTimestamp = String.valueOf(System.currentTimeMillis() / 1000L);
final long blacklisted = Sonar.get().getFallback().getBlacklist().estimatedSize();
final long blacklisted = Sonar.get0().getFallback().getBlacklist().estimatedSize();
// Calculate during-attack-statistics using their deltas
final long totalVerified = Sonar.get().getVerifiedPlayerController().getCache().size();
final long totalVerified = Sonar.get0().getVerifiedPlayerController().getCache().size();
final long verified = Math.max(totalVerified - currentAttack.successfulVerifications, 0);
final long totalFailed = Sonar.get().getStatistics().getTotalFailedVerifications();
final long totalFailed = Sonar.get0().getStatistics().getTotalFailedVerifications();
final long failed = Math.max(totalFailed - currentAttack.failedVerifications, 0);
webhook.post(() -> {
final SonarConfiguration.Webhook.Embed config = Sonar.get().getConfig().getWebhook().getEmbed();
final SonarConfiguration.Webhook.Embed config = Sonar.get0().getConfig().getWebhook().getEmbed();
final String description = config.getDescription()
.replace("<start-timestamp>", startTimestamp)
.replace("<end-timestamp>", endTimestamp)
@@ -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,23 @@ public class UpdateChecker {
final JsonObject json = parseJson(urlConnection);
final String latestStableRelease = json.get("tag_name").getAsString();
final int convertedLatestVersion = convertVersion(latestStableRelease);
final int convertedCurrentVersion = convertVersion(Sonar.get().getVersion().getVersion());
final int convertedCurrentVersion = convertVersion(Sonar.get0().getVersion().getVersion());
if (convertedCurrentVersion < convertedLatestVersion) {
Sonar.get().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/latest");
Sonar.get0().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get0().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get0().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/latest");
lastCheckResult = CheckResult.OUTDATED_VERSION;
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().getGitBranch().equals("main")) {
Sonar.get().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get0().getVersion().getGitBranch().equals("main")) {
Sonar.get0().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get0().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
lastCheckResult = CheckResult.UNSTABLE_VERSION;
} else if (lastCheckResult != CheckResult.LATEST_VERSION) {
Sonar.get().getLogger().info("You are currently using the latest stable release of Sonar!");
Sonar.get0().getLogger().info("You are currently using the latest stable release of Sonar!");
lastCheckResult = CheckResult.LATEST_VERSION;
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Unable to retrieve version information: {}", throwable);
Sonar.get0().getLogger().warn("Unable to retrieve version information: {}", throwable);
lastCheckResult = CheckResult.API_ERROR;
}
});
@@ -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);
-1
View File
@@ -40,7 +40,6 @@ allprojects {
removeUnusedImports()
trimTrailingWhitespace()
indentWithSpaces(2)
licenseHeaderFile("../HEADER")
}
}
@@ -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
@@ -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
@@ -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
@@ -122,6 +122,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
default:
byteBuf.release();
throw ProtocolUtil.DEBUG ? new DecoderException("Bad handshake intent " + handshake.getIntent())
: QuietDecoderException.INSTANCE;
}
@@ -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
@@ -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
@@ -92,7 +92,7 @@ public final class BukkitJoinListener implements Listener {
channel.close();
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
Sonar.get0().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
}
}
}
@@ -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
@@ -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());
@@ -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
@@ -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,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,7 +20,11 @@ package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import lombok.experimental.UtilityClass;
import lombok.val;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
import org.jetbrains.annotations.NotNull;
import sun.misc.Unsafe;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
@@ -34,26 +38,59 @@ import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_
public class FallbackBungeeInjector {
public void inject() {
try {
final Field childField = PipelineUtils.class.getField("SERVER_CHILD");
final Field childField;
try {
// This field only exists on BungeeCord build 1908 and below.
// See https://github.com/SpigotMC/BungeeCord/pull/3776 for more info.
//noinspection all
childField = PipelineUtils.class.getField("SERVER_CHILD");
} catch (NoSuchFieldException exception) {
injectOnModernBungeeCord();
return;
}
childField.setAccessible(true);
// Make sure to store the original channel initializer
final ChannelInitializer<Channel> originalInitializer = PipelineUtils.SERVER_CHILD;
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
final Object base = unsafe.staticFieldBase(childField);
final long offset = unsafe.staticFieldOffset(childField);
// Replace the original channel initializer with our new injected initializer
unsafe.putObject(base, offset, injectedInitializer);
injectOnOldBungeeCord(childField);
} catch (Exception exception) {
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
Sonar.get0().getLogger().error("An error occurred while injecting {}", exception);
}
}
private static void injectOnOldBungeeCord(final @NotNull Field childField) throws Exception {
// Make sure to store the original channel initializer
//noinspection unchecked
val originalInitializer = (ChannelInitializer<Channel>) childField.get(null);
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
final Object base = unsafe.staticFieldBase(childField);
final long offset = unsafe.staticFieldOffset(childField);
// Replace the original channel initializer with our new injected initializer
unsafe.putObject(base, offset, injectedInitializer);
}
// See https://github.com/SpigotMC/BungeeCord/pull/3787
private static void injectOnModernBungeeCord() {
// The order is important...
final BungeeChannelInitializer original = ProxyServer.getInstance().unsafe().getFrontendChannelInitializer();
final BungeeChannelInitializer newInitializer = BungeeChannelInitializer.create(
channel -> {
// https://github.com/SpigotMC/BungeeCord/pull/3787#issuecomment-2661059876
if (!original.getChannelAcceptor().accept(channel)) {
return false;
}
FallbackInjectedChannelInitializer.inject(channel,
pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
new FallbackBungeeInboundHandler()));
return true;
});
ProxyServer.getInstance().unsafe().setFrontendChannelInitializer(newInitializer);
}
}
+2 -2
View File
@@ -2,6 +2,6 @@ dependencies {
compileOnly(project(":api"))
compileOnly(rootProject.libs.imagefilters)
testCompileOnly(project(":api"))
testCompileOnly(rootProject.libs.imagefilters)
testImplementation(project(":api"))
testImplementation(rootProject.libs.imagefilters)
}
@@ -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
@@ -109,12 +109,12 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
// Randomize the colors for the gradient effect
for (int i = 0; i < COLORS.length; i++) {
final float random = 0.9f + RANDOM.nextFloat() * 0.1f;
COLORS[i] = Color.getHSBColor(RANDOM.nextFloat(), random, random);
COLORS[i] = Color.getHSBColor(RANDOM.nextFloat(), random * 0.8f, random);
}
// Apply the random gradient effect
graphics.setPaint(new LinearGradientPaint(0, 0, width, height,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REPEAT));
}
private void drawCharacters(final @NotNull Graphics2D graphics,
@@ -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
@@ -0,0 +1,212 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.legacy;
import com.jhlabs.image.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.captcha.filters.NoiseOverlayFilter;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class LegacyCaptchaGenerator implements CaptchaGenerator {
private static final ScratchFilter SCRATCHES = new ScratchFilter(4);
private static final NoiseOverlayFilter NOISE = new NoiseOverlayFilter(1, 7);
private static final SmearFilter SMEAR = new SmearFilter();
private static final SaturationFilter SATURATION = new SaturationFilter();
private static final RippleFilter RIPPLE = new RippleFilter();
private static final Random RANDOM = new Random();
static {
RIPPLE.setXAmplitude(0);
float yAmplitude = 10 - RANDOM.nextInt(20);
if (Math.abs(yAmplitude) < 5) {
yAmplitude = yAmplitude >= 0 ? 5 : -5;
}
RIPPLE.setYAmplitude(yAmplitude);
SATURATION.setAmount(0.45f + RANDOM.nextFloat() * 0.15f);
SMEAR.setShape(SmearFilter.CIRCLES);
SMEAR.setMix(0.15f);
SMEAR.setDensity(0.1f);
SMEAR.setDistance(5);
}
private final int width = 128, height = 128;
private final @Nullable File background;
private @Nullable BufferedImage backgroundImage;
private GradientPaint gradient;
public BufferedImage createCachedBackgroundImage() {
// Create a new image buffer with a 3-byte color spectrum
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
if (background == null) {
if (backgroundImage != null) {
return backgroundImage;
}
// Don't use any special background image
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Fill the entire background image with a noise texture
image = new CausticsFilter().filter(image, null);
// Adjust the saturation of the randomly generated background noise
final SaturationFilter saturationFilter = new SaturationFilter();
saturationFilter.setAmount(0.4f + RANDOM.nextFloat() * 0.2f);
image = saturationFilter.filter(image, null);
// Un-sharpen the background a bit
image = new UnsharpFilter().filter(image, null);
return image;
}
// Try to load the image from a file if it doesn't exist yet
if (backgroundImage == null) {
try {
backgroundImage = ImageIO.read(background);
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
image.createGraphics().drawImage(backgroundImage, 0, 0, Color.WHITE, null);
return image;
}
private @NotNull BufferedImage mergeImages(final @NotNull BufferedImage background,
final @NotNull BufferedImage foreground) {
// Get the background image and create a new foreground image
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
// Create a new image with transparency for the merged result
final Graphics2D graphics = image.createGraphics();
// Draw the foreground image on top of the background at specified coordinates
graphics.drawImage(background, 0, 0, null);
// Set AlphaComposite to handle transparency for the foreground image
graphics.setComposite(AlphaComposite.SrcOver);
// Draw the foreground image on top of the background at specified coordinates
graphics.drawImage(foreground, 0, 0, null);
graphics.dispose();
return image;
}
@Override
public @NotNull BufferedImage createImage(final char @NotNull [] answer) {
final Color color0 = Color.getHSBColor(RANDOM.nextFloat(), 1, 1);
final Color color1 = Color.getHSBColor(RANDOM.nextFloat(), 1, 0.5f);
gradient = new GradientPaint(0, 0, color0, width, height, color1);
// Get the background image and create a new foreground image
BufferedImage foreground = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphics = foreground.createGraphics();
final FontRenderContext ctx = graphics.getFontRenderContext();
// Change some rendering hints for anti aliasing
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw characters
drawCharacters(graphics, ctx, answer);
// Make sure to dispose the graphics after using it
graphics.dispose();
// Apply any given filter to the foreground
new BumpFilter().filter(foreground, foreground);
SMEAR.filter(foreground, foreground);
RIPPLE.filter(foreground, foreground);
SCRATCHES.filter(foreground, foreground);
final BufferedImage merged = mergeImages(createCachedBackgroundImage(), foreground);
NOISE.transform(merged);
return merged;
}
private void drawCharacters(final @NotNull Graphics2D graphics,
final @NotNull FontRenderContext ctx,
final char[] answer) {
// Create font render context
final int fontSize = 62 - answer.length * 2;
final Font font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
// Calculate string width
final double stringWidth = font.getStringBounds(answer, 0, answer.length, ctx).getWidth();
// Calculate character positions
int beginX = (int) ((width - stringWidth) / 2D + stringWidth / (answer.length * 2.5D));
final int beginY = (int) ((height + fontSize / 2D) / 2D);
double rotation = 0;
if (gradient != null) {
graphics.setPaint(gradient);
}
// Draw each character one by one
for (final char character : answer) {
// Create a glyph vector for the character
final GlyphVector glyphVector = font.createGlyphVector(ctx, String.valueOf(character));
final int visualBoundsWidth = (int) glyphVector.getVisualBounds().getWidth();
if (gradient == null) {
Objects.requireNonNull(backgroundImage);
// Use the inverse color by checking the background image
// Then, create a gradient for the text by using the colors
final int x0 = Math.min(Math.max(beginX + 5 /* small threshold */, 0), width);
final int x1 = Math.min(Math.max(beginX + (int) visualBoundsWidth, 0), width);
final int y = beginY + fontSize / 2;
final Color color0 = new Color(~backgroundImage.getRGB(x0, y));
final Color color1 = new Color(~backgroundImage.getRGB(x1, y));
final GradientPaint gradient = new GradientPaint(0, 0, color0, width, height, color1);
graphics.setPaint(gradient);
}
// Apply a transformation to the glyph vector using AffineTransform
final AffineTransform transformation = AffineTransform.getTranslateInstance(beginX, beginY);
// Add a bit of randomization to the rotation
rotation += Math.toRadians(6 - RANDOM.nextInt(12));
transformation.rotate(rotation);
transformation.scale(1, 1.25);
// Draw the glyph to the buffered image
final Shape transformedShape = transformation.createTransformedShape(glyphVector.getOutline());
graphics.fill(transformedShape);
// Add text outline/shadow to confuse an AI's edge detection
addTextOutline(graphics, transformedShape);
// Update next X position
beginX += visualBoundsWidth + 2;
}
}
private static void addTextOutline(final @NotNull Graphics2D graphics,
final @NotNull Shape transformedShape) {
// Create a stroked copy of the text and slightly offset/distort it
final Shape strokedShape = new BasicStroke().createStrokedShape(transformedShape);
final double tx = 0.5 + RANDOM.nextDouble();
final double ty = 0.5 + RANDOM.nextDouble();
// Draw the stroked shape
final AffineTransform transform = AffineTransform.getTranslateInstance(tx, ty);
graphics.fill(transform.createTransformedShape(strokedShape));
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.legacy;
import com.jhlabs.image.TransformFilter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public final class RippleFilter extends TransformFilter {
private float xAmplitude, yAmplitude;
@Override
protected void transformInverse(final int x, final int y, final float @NotNull [] out) {
final float nx = (float) y / 13f;
final float ny = (float) x / 13f;
final float fx = (float) Math.sin(nx);
final float fy = (float) Math.sin(ny);
out[0] = (float) x + xAmplitude * fx;
out[1] = (float) y + yAmplitude * fy;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2025 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.legacy;
import com.jhlabs.image.AbstractBufferedImageOp;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@RequiredArgsConstructor
public final class ScratchFilter extends AbstractBufferedImageOp {
private static final Random RANDOM = new Random();
private final int amount;
@Override
public @NotNull BufferedImage filter(final @NotNull BufferedImage src, final BufferedImage dst) {
final Graphics2D graphics = src.createGraphics();
// Apply some gradient effect on them
final Color color0 = Color.getHSBColor(RANDOM.nextFloat(), RANDOM.nextFloat(), 1);
final Color color1 = new Color(~color0.getRGB());
final GradientPaint gradient = new GradientPaint(0, 0, color0, src.getWidth(), src.getHeight(), color1);
graphics.setPaint(gradient);
final float halfWidth = src.getWidth() * 0.5f;
for (int i = 0; i < amount; ++i) {
final float randomX = src.getWidth() * RANDOM.nextFloat();
final float randomY = src.getHeight() * RANDOM.nextFloat();
final float amplitude = 6.2831855f * (RANDOM.nextFloat() - 0.5f);
final float sin = (float) Math.sin(amplitude) * halfWidth;
final float cos = (float) Math.cos(amplitude) * halfWidth;
final float x1 = randomX - cos;
final float y1 = randomY - sin;
final float x2 = randomX + cos;
final float y2 = randomY + sin;
graphics.drawLine((int) x1, (int) y1, (int) x2, (int) y2);
}
graphics.dispose();
return src;
}
}
+1 -1
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
@@ -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.0")
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
.build(),
// ORMLite
@@ -72,19 +72,19 @@ class LibraryLoader {
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-minimessage")
.version("4.17.0")
.version("4.13.1") // adventure-platforms hasn't updated in years... Please update! :'(
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-serializer-gson")
.version("4.17.0")
.version("4.18.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-nbt")
.version("4.17.0")
.version("4.18.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.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
@@ -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
@@ -55,11 +55,11 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
// Account for this by checking if the inetAddress has been set yet
if (inetAddress != null) {
// Remove the IP address from the queue
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (ignored, action) -> {
Sonar.get0().getFallback().getQueue().getPlayers().compute(inetAddress, (ignored, action) -> {
// The player is not queued, so we need to remove them from other maps as well
if (action == null) {
// Remove the IP address from the connected players, if needed
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> {
Sonar.get0().getFallback().getConnected().compute(inetAddress, (__, v) -> {
/*
* Remove this account from the online players or decrement the number of accounts
* with the same IP, but only if the player is logging into the backend server.
@@ -67,7 +67,7 @@ public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
* we've never actually incremented it in the first place ¯\_(ツ)_/¯
*/
if (v == null) {
Sonar.get().getFallback().getOnline().compute(inetAddress,
Sonar.get0().getFallback().getOnline().compute(inetAddress,
(ignored1, count) -> count == null || count <= 1 ? null : count - 1);
}
return null;
@@ -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
@@ -97,22 +97,22 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
// Check if Fallback is already verifying a player with the same IP address
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
if (Sonar.get0().getFallback().getConnected().containsKey(inetAddress)) {
customDisconnect(ctx.channel(), alreadyVerifying, protocolVersion);
return;
}
// Check if the protocol ID of the player is not allowed to enter the server
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
if (Sonar.get0().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
return;
}
final String hostAddress = inetAddress.getHostAddress();
// Check if the player failed the verification too many times
final int limit = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
final int limit = Sonar.get0().getConfig().getVerification().getBlacklistThreshold();
if (limit > 0) {
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
final int score = Sonar.get0().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
@@ -120,27 +120,27 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
if (!Sonar.get0().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
if (geyser && !Sonar.get0().getConfig().getVerification().isCheckGeyser()) {
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Make sure we actually have to verify the player
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
if (Sonar.get0().getVerifiedPlayerController().getCache().contains(fingerprint)) {
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Check if the IP address is currently being rate-limited
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
if (!Sonar.get0().getFallback().getRatelimiter().attempt(inetAddress)) {
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -149,7 +149,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
rewriteProtocol(ctx, channelRemovalListener);
// Queue the connection for further processing
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
Sonar.get0().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
// Check if the player is already queued since we don't want bots to flood the queue
if (runnable != null) {
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
@@ -168,8 +168,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
protected final void initialLogin(final @NotNull Channel channel,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
Sonar.get0().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get0().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
@@ -206,8 +206,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
}
// Add our read/write timeout handler
ctx.pipeline().addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
Sonar.get().getConfig().getVerification().getReadTimeout(),
Sonar.get().getConfig().getVerification().getWriteTimeout(),
Sonar.get0().getConfig().getVerification().getReadTimeout(),
Sonar.get0().getConfig().getVerification().getWriteTimeout(),
TimeUnit.MILLISECONDS));
}
@@ -231,11 +231,11 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
final @NotNull ProtocolVersion protocolVersion) {
// TODO: recode this
// Remove the connection handler pipeline to completely take over the channel
final String handler = Sonar.get().getPlatform().getConnectionHandler();
final String handler = Sonar.get0().getPlatform().getConnectionHandler();
if (channel.pipeline().context(handler) != null) {
channel.pipeline().remove(handler);
}
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
final String encoder = Sonar.get0().getPlatform().getEncoder().apply(channel.pipeline());
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
// Close the channel if no encoder exists
if (currentEncoder != null) {
@@ -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
@@ -76,18 +76,18 @@ public final class FallbackUserWrapper implements FallbackUser {
GlobalSonarStatistics.totalAttemptedVerifications++;
if (Sonar.get().getConfig().getVerification().isLogConnections()
&& (Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack())) {
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
if (Sonar.get0().getConfig().getVerification().isLogConnections()
&& (Sonar.get0().getAttackTracker().getCurrentAttack() == null
|| Sonar.get0().getConfig().getVerification().isLogDuringAttack())) {
Sonar.get0().getLogger().info(
Sonar.get0().getConfig().getMessagesConfig().getString("verification.logs.connection")
.replace("<username>", username)
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
.replace("<ip>", Sonar.get0().getConfig().formatAddress(inetAddress))
.replace("<protocol>", protocolVersion.getName()));
}
// Call the VerifyJoinEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(this));
Sonar.get0().getEventManager().publish(new UserVerifyJoinEvent(this));
// Run this in the channel's event loop to avoid issues
channel.eventLoop().execute(() -> {
@@ -96,17 +96,21 @@ public final class FallbackUserWrapper implements FallbackUser {
return;
}
// How? Is there some kind of de-sync or race condition?
if (channel.pipeline().context(FALLBACK_FRAME_ENCODER) != null) {
channel.close(); // Nope ¯\_(ツ)_/¯
return;
}
// Mark the player as connected by caching them in a map of verifying players
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
Sonar.get0().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
// 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);
@@ -127,7 +131,6 @@ public final class FallbackUserWrapper implements FallbackUser {
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);
});
}
@@ -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
@@ -60,13 +60,17 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
throw new ReflectiveOperationException(throwable);
}
inject(channel, sonarPipelineInjector);
}
public static void inject(final @NotNull Channel channel, final Consumer<ChannelPipeline> sonarPipelineInjector) {
// Inject Sonar's channel handler into the pipeline;
// Also make sure the player is not a fake player to avoid compatibility issues
if (channel.isActive() && !FakeChannelUtil.isFakeChannel(channel)) {
final FallbackInboundHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
// We need to be careful on Bukkit, as the encoder can be different
if (Sonar.get().getPlatform() == SonarPlatform.BUKKIT) {
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
if (Sonar.get0().getPlatform() == SonarPlatform.BUKKIT) {
final String encoder = Sonar.get0().getPlatform().getEncoder().apply(channel.pipeline());
channel.pipeline().addBefore(encoder, FALLBACK_INBOUND_HANDLER, inboundHandler);
} else {
channel.pipeline().addFirst(FALLBACK_INBOUND_HANDLER, inboundHandler);
@@ -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
@@ -44,12 +44,12 @@ public class CaptchaPreparer {
}
final SystemTimer timer = new SystemTimer();
Sonar.get().getLogger().info("Asynchronously preparing CAPTCHA answers...");
Sonar.get().getLogger().info("Players will be able to join even if the preparation isn't finished");
Sonar.get0().getLogger().info("Asynchronously preparing CAPTCHA answers...");
Sonar.get0().getLogger().info("Players will be able to join even if the preparation isn't finished");
// Prepare cache
final int precomputeAmount = Sonar.get().getConfig().getVerification().getMap().getPrecomputeAmount();
final char[] alphabet = Sonar.get().getConfig().getVerification().getMap().getAlphabet().toCharArray();
final int precomputeAmount = Sonar.get0().getConfig().getVerification().getMap().getPrecomputeAmount();
final char[] alphabet = Sonar.get0().getConfig().getVerification().getMap().getAlphabet().toCharArray();
preparedAmount = 0;
cached = new MapCaptchaInfo[precomputeAmount];
@@ -62,13 +62,13 @@ public class CaptchaPreparer {
for (int j = 0; j < answer.length; j++) {
answer[j] = alphabet[RANDOM.nextInt(alphabet.length)];
}
final BufferedImage image = Sonar.get().getFallback().getCaptchaGenerator().createImage(answer);
final BufferedImage image = Sonar.get0().getFallback().getCaptchaGenerator().createImage(answer);
// Convert and cache converted Minecraft map bytes
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), MapColorPalette.imageToBuffer(image));
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
Sonar.get().getEventManager().publish(new CaptchaGenerationEndEvent(timer, preparedAmount));
Sonar.get0().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
Sonar.get0().getEventManager().publish(new CaptchaGenerationEndEvent(timer, preparedAmount));
});
}
@@ -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
@@ -66,7 +66,7 @@ public enum FallbackPacketRegistry {
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x01, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_20_5, true));
clientbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
clientbound.register(FinishConfigurationPacket.class, () -> FinishConfigurationPacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, true),
map(0x03, MINECRAFT_1_20_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
@@ -81,7 +81,7 @@ public enum FallbackPacketRegistry {
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false),
map(0x02, MINECRAFT_1_20_5, false));
serverbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
serverbound.register(FinishConfigurationPacket.class, () -> FinishConfigurationPacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
@@ -106,7 +106,8 @@ public enum FallbackPacketRegistry {
map(0x28, MINECRAFT_1_19_4, true),
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true),
map(0x2C, MINECRAFT_1_21_2, true));
map(0x2C, MINECRAFT_1_21_2, true),
map(0x2B, MINECRAFT_1_21_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, true),
map(0x1F, MINECRAFT_1_9, true),
@@ -122,7 +123,8 @@ public enum FallbackPacketRegistry {
map(0x23, MINECRAFT_1_19_4, true),
map(0x24, MINECRAFT_1_20_2, true),
map(0x26, MINECRAFT_1_20_5, true),
map(0x27, MINECRAFT_1_21_2, true));
map(0x27, MINECRAFT_1_21_2, true),
map(0x26, MINECRAFT_1_21_5, true));
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
map(0x1A, MINECRAFT_1_9, true),
@@ -137,7 +139,8 @@ public enum FallbackPacketRegistry {
map(0x17, MINECRAFT_1_19_3, true),
map(0x1A, MINECRAFT_1_19_4, true),
map(0x1B, MINECRAFT_1_20_2, true),
map(0x1D, MINECRAFT_1_20_5, true));
map(0x1D, MINECRAFT_1_20_5, true),
map(0x1C, MINECRAFT_1_21_5, true));
clientbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x08, MINECRAFT_1_7_2, true),
map(0x2E, MINECRAFT_1_9, true),
@@ -154,7 +157,8 @@ public enum FallbackPacketRegistry {
map(0x3C, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_20_2, true),
map(0x40, MINECRAFT_1_20_5, true),
map(0x42, MINECRAFT_1_21_2, true));
map(0x42, MINECRAFT_1_21_2, true),
map(0x41, MINECRAFT_1_21_5, true));
clientbound.register(PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new,
map(0x39, MINECRAFT_1_7_2, true),
map(0x2B, MINECRAFT_1_9, true),
@@ -171,7 +175,8 @@ public enum FallbackPacketRegistry {
map(0x34, MINECRAFT_1_19_4, true),
map(0x36, MINECRAFT_1_20_2, true),
map(0x38, MINECRAFT_1_20_5, true),
map(0x3A, MINECRAFT_1_21_2, true));
map(0x3A, MINECRAFT_1_21_2, true),
map(0x39, MINECRAFT_1_21_5, true));
clientbound.register(ChunkDataPacket.class, ChunkDataPacket::new,
map(0x21, MINECRAFT_1_7_2, true),
map(0x20, MINECRAFT_1_9, true),
@@ -187,7 +192,8 @@ public enum FallbackPacketRegistry {
map(0x24, MINECRAFT_1_19_4, true),
map(0x25, MINECRAFT_1_20_2, true),
map(0x27, MINECRAFT_1_20_5, true),
map(0x28, MINECRAFT_1_21_2, true));
map(0x28, MINECRAFT_1_21_2, true),
map(0x27, MINECRAFT_1_21_5, true));
clientbound.register(RemoveEntitiesPacket.class, RemoveEntitiesPacket::new,
map(0x13, MINECRAFT_1_7_2, true),
map(0x30, MINECRAFT_1_9, true),
@@ -206,7 +212,8 @@ public enum FallbackPacketRegistry {
map(0x3E, MINECRAFT_1_19_4, true),
map(0x40, MINECRAFT_1_20_2, true),
map(0x42, MINECRAFT_1_20_5, true),
map(0x47, MINECRAFT_1_21_2, true));
map(0x47, MINECRAFT_1_21_2, true),
map(0x46, MINECRAFT_1_21_5, true));
clientbound.register(SetPassengersPacket.class, SetPassengersPacket::new,
map(0x1B, MINECRAFT_1_7_2, true),
map(0x40, MINECRAFT_1_9, true),
@@ -222,22 +229,8 @@ public enum FallbackPacketRegistry {
map(0x5B, MINECRAFT_1_20_2, true),
map(0x5D, MINECRAFT_1_20_3, true),
map(0x5F, MINECRAFT_1_20_5, true),
map(0x65, MINECRAFT_1_21_2, true));
clientbound.register(VehicleMovePacket.class, VehicleMovePacket::new,
map(0x29, MINECRAFT_1_9, true),
map(0x2B, MINECRAFT_1_13, true),
map(0x2C, MINECRAFT_1_14, true),
map(0x2D, MINECRAFT_1_15, true),
map(0x2C, MINECRAFT_1_16, true),
map(0x2B, MINECRAFT_1_16_2, true),
map(0x2C, MINECRAFT_1_17, true),
map(0x29, MINECRAFT_1_19, true),
map(0x2B, MINECRAFT_1_19_1, true),
map(0x2A, MINECRAFT_1_19_3, true),
map(0x2E, MINECRAFT_1_19_4, true),
map(0x2F, MINECRAFT_1_20_2, true),
map(0x31, MINECRAFT_1_20_5, true),
map(0x33, MINECRAFT_1_21_2, true));
map(0x65, MINECRAFT_1_21_2, true),
map(0x64, MINECRAFT_1_21_5, true));
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
@@ -257,7 +250,8 @@ public enum FallbackPacketRegistry {
map(0x45, MINECRAFT_1_20_2, true),
map(0x47, MINECRAFT_1_20_3, true),
map(0x49, MINECRAFT_1_20_5, true),
map(0x4E, MINECRAFT_1_21_2, true));
map(0x4E, MINECRAFT_1_21_2, true),
map(0x4D, MINECRAFT_1_21_5, true));
clientbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x32, MINECRAFT_1_7_2, true),
map(0x11, MINECRAFT_1_9, true),
@@ -272,7 +266,8 @@ public enum FallbackPacketRegistry {
map(0x32, MINECRAFT_1_19_4, true),
map(0x33, MINECRAFT_1_20_2, true),
map(0x35, MINECRAFT_1_20_5, true),
map(0x37, MINECRAFT_1_21_2, true));
map(0x37, MINECRAFT_1_21_2, true),
map(0x36, MINECRAFT_1_21_5, true));
clientbound.register(SetDefaultSpawnPositionPacket.class, SetDefaultSpawnPositionPacket::new,
map(0x05, MINECRAFT_1_7_2, true),
map(0x43, MINECRAFT_1_9, true),
@@ -290,7 +285,8 @@ public enum FallbackPacketRegistry {
map(0x52, MINECRAFT_1_20_2, true),
map(0x54, MINECRAFT_1_20_3, true),
map(0x56, MINECRAFT_1_20_5, true),
map(0x5B, MINECRAFT_1_21_2, true));
map(0x5B, MINECRAFT_1_21_2, true),
map(0x5A, MINECRAFT_1_21_5, true));
clientbound.register(MapDataPacket.class, MapDataPacket::new,
map(0x34, MINECRAFT_1_7_2, true),
map(0x24, MINECRAFT_1_9, true),
@@ -305,7 +301,8 @@ public enum FallbackPacketRegistry {
map(0x29, MINECRAFT_1_19_4, true),
map(0x2A, MINECRAFT_1_20_2, true),
map(0x2C, MINECRAFT_1_20_5, true),
map(0x2D, MINECRAFT_1_21_2, true));
map(0x2D, MINECRAFT_1_21_2, true),
map(0x2C, MINECRAFT_1_21_5, true));
clientbound.register(SetContainerSlotPacket.class, SetContainerSlotPacket::new,
map(0x2F, MINECRAFT_1_7_2, true),
map(0x16, MINECRAFT_1_9, true),
@@ -318,7 +315,8 @@ public enum FallbackPacketRegistry {
map(0x13, MINECRAFT_1_19, true),
map(0x12, MINECRAFT_1_19_3, true),
map(0x14, MINECRAFT_1_19_4, true),
map(0x15, MINECRAFT_1_20_2, true));
map(0x15, MINECRAFT_1_20_2, true),
map(0x14, MINECRAFT_1_21_5, true));
clientbound.register(SetExperiencePacket.class, SetExperiencePacket::new,
map(0x1F, MINECRAFT_1_7_2, true),
map(0x3D, MINECRAFT_1_9, true),
@@ -334,7 +332,8 @@ public enum FallbackPacketRegistry {
map(0x58, MINECRAFT_1_20_2, true),
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true),
map(0x61, MINECRAFT_1_21_2, true));
map(0x61, MINECRAFT_1_21_2, true),
map(0x60, MINECRAFT_1_21_5, true));
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
@@ -349,11 +348,13 @@ public enum FallbackPacketRegistry {
map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true));
map(0x73, MINECRAFT_1_21_2, true),
map(0x72, MINECRAFT_1_21_5, true));
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true),
map(0x23, MINECRAFT_1_21_2, true));
map(0x23, MINECRAFT_1_21_2, true),
map(0x22, MINECRAFT_1_21_5, true));
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, true),
map(0x7A, MINECRAFT_1_21_2, true));
@@ -374,7 +375,8 @@ public enum FallbackPacketRegistry {
map(0x4F, MINECRAFT_1_20_2, true),
map(0x51, MINECRAFT_1_20_3, true),
map(0x53, MINECRAFT_1_20_5, true),
map(0x63, MINECRAFT_1_21_2, true));
map(0x63, MINECRAFT_1_21_2, true),
map(0x62, MINECRAFT_1_21_5, true));
clientbound.register(EntityAnimationPacket.class, EntityAnimationPacket::new,
map(0x0B, MINECRAFT_1_7_2, true),
map(0x06, MINECRAFT_1_9, true),
@@ -382,7 +384,8 @@ public enum FallbackPacketRegistry {
map(0x06, MINECRAFT_1_17, true),
map(0x03, MINECRAFT_1_19, true),
map(0x04, MINECRAFT_1_19_4, true),
map(0x03, MINECRAFT_1_20_2, true));
map(0x03, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_21_5, true));
clientbound.register(UpdateTimePacket.class, UpdateTimePacket::new,
map(0x03, MINECRAFT_1_7_2, true),
map(0x44, MINECRAFT_1_9, true),
@@ -400,7 +403,8 @@ public enum FallbackPacketRegistry {
map(0x60, MINECRAFT_1_20_2, true),
map(0x62, MINECRAFT_1_20_3, true),
map(0x64, MINECRAFT_1_20_5, true),
map(0x6B, MINECRAFT_1_21_2, true));
map(0x6B, MINECRAFT_1_21_2, true),
map(0x6A, MINECRAFT_1_21_5, true));
serverbound.register(ClientTickEndPacket.class, ClientTickEndPacket::new,
map(0x0B, MINECRAFT_1_21_2, false));
@@ -625,7 +629,8 @@ public enum FallbackPacketRegistry {
map(0x33, MINECRAFT_1_20_3, false),
map(0x36, MINECRAFT_1_20_5, false),
map(0x38, MINECRAFT_1_21_2, false),
map(0x3A, MINECRAFT_1_21_4, false));
map(0x3A, MINECRAFT_1_21_4, false),
map(0x3B, MINECRAFT_1_21_5, false));
}
};
@@ -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
@@ -50,7 +50,7 @@ public final class FallbackPacketSnapshot implements FallbackPacket {
try {
originalPacket.encode(byteBuf, protocolVersion);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not encode packet {} for version {}: {}",
Sonar.get0().getLogger().error("Could not encode packet {} for version {}: {}",
originalPacket, protocolVersion, throwable);
break;
}
@@ -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,18 +28,19 @@ import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationStartEvent;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.captcha.legacy.LegacyCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionType;
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
import xyz.jonesdev.sonar.common.fallback.protocol.item.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.RegistryDataPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
import java.io.File;
import java.util.Random;
import java.util.UUID;
@@ -75,15 +76,26 @@ public class FallbackPreparer {
public final FallbackPacket NO_MOVE_ABILITIES_BEDROCK = new PlayerAbilitiesPacket(0x06, 0, 0);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
SPAWN_X_POSITION, 10000, SPAWN_Z_POSITION, 0, 90, 0, 0, false, false, true));
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket();
// I think the people working at Microsoft are actually the *real* ultimate trolls of the internet!
public final FallbackPacket[] EMPTY_CHUNK_DATA = new FallbackPacket[]{
new FallbackPacketSnapshot(new ChunkDataPacket(0, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(0, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(0, -1)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(1, -1)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, 0)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, 1)),
new FallbackPacketSnapshot(new ChunkDataPacket(-1, -1))
};
public final FallbackPacket PRE_JOIN_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(PRE_JOIN_KEEP_ALIVE_ID));
public final FallbackPacket[] REGISTRY_SYNC_1_20 = new FallbackPacket[] {
new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null))};
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20_5);
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
public final FallbackPacket[] REGISTRY_SYNC_1_21_2 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_2);
public final FallbackPacket[] REGISTRY_SYNC_1_21_4 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_4);
public final FallbackPacket[] REGISTRY_SYNC_1_21_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21_5);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket INVALID_HELD_ITEM_SLOT = new SetHeldItemPacket(-1);
public final FallbackPacket RANDOM_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(RANDOM.nextInt()));
@@ -119,15 +131,15 @@ public class FallbackPreparer {
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@SuppressWarnings("ResultOfMethodCallIgnored")
public void prepare() {
// Preload the packet registry to avoid CPU/RAM issues on 1st connection
Sonar.get().getLogger().info("Preloading all registered packets...");
Sonar.get0().getLogger().info("Preloading all registered packets...");
//noinspection ResultOfMethodCallIgnored
FallbackPacketRegistry.values();
// Prepare LoginSuccess packet with capped username to 16 characters
final UUID uuid = UUID.randomUUID();
String username = Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username");
String username = Sonar.get0().getConfig().getGeneralConfig().getString("verification.cached-username");
if (username.length() > 16) {
username = username.substring(0, 16);
}
@@ -135,16 +147,16 @@ public class FallbackPreparer {
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(),
Sonar.get0().getConfig().getVerification().getGamemode().getId(),
-1, 0, 0,
RANDOM.nextInt(3), 1, 0, 0,
new String[]{"lol"}, "lol", "flat",
new String[]{"minecraft:overworld"}, "minecraft:overworld", "flat",
DimensionType.OVERWORLD, RANDOM.nextLong() & 1337,
false, true, false,
false, false, false, true));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
maxMovementTick = Sonar.get0().getConfig().getVerification().getGravity().getMaxMovementTicks();
double motionY = 0, fallDistance = 0;
@@ -184,25 +196,25 @@ public class FallbackPreparer {
}
// Prepare disconnect packets during login
blacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
unsupportedVersion = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getVerification().getUnsupportedVersion(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
blacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getTooFastReconnect(), true));
unsupportedVersion = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getVerification().getUnsupportedVersion(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(new DisconnectPacket(Sonar.get0().getConfig().getTooManyOnlinePerIp(), true));
// Prepare transfer packet
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
if (Sonar.get0().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
transferToOrigin = new FallbackPacketSnapshot(new TransferPacket(
Sonar.get().getConfig().getGeneralConfig().getString("verification.transfer.destination-host"),
Sonar.get().getConfig().getGeneralConfig().getInt("verification.transfer.destination-port")));
Sonar.get0().getConfig().getGeneralConfig().getString("verification.transfer.destination-host"),
Sonar.get0().getConfig().getGeneralConfig().getInt("verification.transfer.destination-port")));
} else {
transferToOrigin = null;
}
// Prepare update time packet
final int timeOfDay = Sonar.get().getConfig().getVerification().getTimeOfDay();
final int timeOfDay = Sonar.get0().getConfig().getVerification().getTimeOfDay();
if (timeOfDay != 1000) {
updateTime = new UpdateTimePacket(0L, timeOfDay, false);
} else {
@@ -210,29 +222,29 @@ public class FallbackPreparer {
}
// If the welcome message is empty, we don't need to send a message to the player
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
final String welcome = Sonar.get0().getConfig().getMessagesConfig().getString("verification.welcome");
if (welcome.isEmpty()) {
welcomeMessage = null;
} else {
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(welcome,
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
if (Sonar.get0().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get0().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
// Prepare CAPTCHA messages
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
incorrectCaptcha = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
Sonar.get0().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
Placeholder.component("prefix", Sonar.get0().getConfig().getPrefix())))));
// Prepare countdown
xpCountdown = new FallbackPacket[Sonar.get().getConfig().getVerification().getMap().getMaxDuration() / 1000];
xpCountdown = new FallbackPacket[Sonar.get0().getConfig().getVerification().getMap().getMaxDuration() / 1000];
for (int i = 0; i < xpCountdown.length; i++) {
final float bar = (float) i / xpCountdown.length;
@@ -241,15 +253,19 @@ public class FallbackPreparer {
// Update the CAPTCHA generator if necessary
final CaptchaGenerationStartEvent generationStartEvent = new CaptchaGenerationStartEvent(
Sonar.get().getFallback().getCaptchaGenerator());
Sonar.get().getEventManager().publish(generationStartEvent);
Sonar.get0().getFallback().getCaptchaGenerator());
Sonar.get0().getEventManager().publish(generationStartEvent);
if (generationStartEvent.getCaptchaGenerator() == null
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator
|| generationStartEvent.getCaptchaGenerator() instanceof LegacyCaptchaGenerator) {
final File backgroundImage = Sonar.get0().getConfig().getVerification().getMap().getBackgroundImage();
final boolean useLegacyCaptchaGenerator = Sonar.get0().getConfig().getGeneralConfig()
.getString("verification.checks.map-captcha.style").equalsIgnoreCase("legacy");
Sonar.get0().getFallback().setCaptchaGenerator(useLegacyCaptchaGenerator
? new LegacyCaptchaGenerator(backgroundImage) : new StandardCaptchaGenerator(backgroundImage));
} else {
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
Sonar.get0().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare CAPTCHA answers
@@ -259,18 +275,20 @@ public class FallbackPreparer {
enterCodeMessage = null;
incorrectCaptcha = null;
xpCountdown = null;
Sonar.get().getFallback().setCaptchaGenerator(null);
Sonar.get0().getFallback().setCaptchaGenerator(null);
}
maxTotalPacketsSent = maxMovementTick + 2
+ (xpCountdown == null ? 0 : xpCountdown.length) * 20 + 5
+ Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get().getConfig().getVerification().getMap().getMaxTries()
+ Sonar.get0().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get0().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
}
public static FallbackPacket[] getRegistryPackets(final @NotNull ProtocolVersion protocolVersion) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_4)) {
if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_5)) {
return REGISTRY_SYNC_1_21_5;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_4)) {
return REGISTRY_SYNC_1_21_4;
} else if (protocolVersion.greaterThanOrEquals(ProtocolVersion.MINECRAFT_1_21_2)) {
return REGISTRY_SYNC_1_21_2;
@@ -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
@@ -72,7 +72,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 7619;
}
return 8163;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8163;
}
return 8173;
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
@@ -118,7 +121,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 10749;
}
return 11285;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11293;
}
return 11303;
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -157,7 +163,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 7644;
}
return 8188;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8185;
}
return 8195;
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -199,7 +208,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 9462;
}
return 10005;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 10006;
}
return 10016;
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -238,7 +250,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 8152;
}
return 8696;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 8696;
}
return 8706;
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -280,7 +295,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 11566;
}
return 12116;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 12110;
}
return 12120;
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_12_2)) {
@@ -322,7 +340,10 @@ public enum BlockType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 11063;
}
return 11607;
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 11607;
}
return 11617;
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> id;
@@ -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
@@ -35,9 +35,11 @@ public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_19_1;
public final CompoundBinaryTag CODEC_1_19_4;
public final CompoundBinaryTag CODEC_1_20;
public final CompoundBinaryTag CODEC_1_20_5;
public final CompoundBinaryTag CODEC_1_21;
public final CompoundBinaryTag CODEC_1_21_2;
public final CompoundBinaryTag CODEC_1_21_4;
public final CompoundBinaryTag CODEC_1_21_5;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
@@ -47,16 +49,18 @@ public final class DimensionRegistry {
CODEC_1_19_1 = getCodec("codec_1_19_1.nbt");
CODEC_1_19_4 = getCodec("codec_1_19_4.nbt");
CODEC_1_20 = getCodec("codec_1_20.nbt");
CODEC_1_20_5 = getCodec("codec_1_20_5.nbt");
CODEC_1_21 = getCodec("codec_1_21.nbt");
CODEC_1_21_2 = getCodec("codec_1_21_2.nbt");
CODEC_1_21_4 = getCodec("codec_1_21_4.nbt");
CODEC_1_21_5 = getCodec("codec_1_21_5.nbt");
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
try (final InputStream inputStream = Sonar.class.getResourceAsStream("/assets/codecs/" + fileName)) {
return BinaryTagIO.reader().read(Objects.requireNonNull(inputStream), BinaryTagIO.Compression.GZIP);
return BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(inputStream), BinaryTagIO.Compression.GZIP);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Could not load mappings for {}: {}", fileName, throwable);
Sonar.get0().getLogger().error("Could not load mappings for {}: {}", fileName, throwable);
throw new IllegalStateException(throwable);
}
}
@@ -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,9 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum DimensionType {
OVERWORLD("minecraft:overworld", 0, 0),
@Deprecated
THE_NETHER("minecraft:the_nether", -1, 1),
@Deprecated
THE_END("minecraft:the_end", 1, 2);
private final String key;
@@ -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
@@ -73,8 +73,19 @@ public enum ItemType {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_2)) {
return 1022;
}
return 1031;
}, protocolVersion -> protocolVersion.greaterThanOrEquals(MINECRAFT_1_21_2) ? 36 : 26);
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 1031;
}
return 1042;
}, protocolVersion -> {
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21)) {
return 26;
}
if (protocolVersion.lessThanOrEquals(MINECRAFT_1_21_4)) {
return 36;
}
return 37;
});
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Integer> components;
@@ -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
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff. Show more