Compare commits

...
22 Commits
Author SHA1 Message Date
Jones e6c3dc9d2b Release 2.1.2 (#368) 2024-08-28 13:06:04 +02:00
Jones a3e98e5e51 Recode CAPTCHA generation (#367)
* feat: fully recode CAPTCHA generation

* style: remove unused explicit cast

* fix(captcha): increase color brightness of second gradient

* style: fix some comments

* chore: fix copyright headers

* refactor: move ttf font loading into separate class

* style: fix some comments

* refactor(captcha): move to array for glyphs

* feat(captcha): recode color gradient algorithm

* fix(captcha): only randomize stroke width once

* feat(captcha): re-add custom background support

* feat(fallback): optimize pre-queue process

* chore: minor code improvements

* feat: update messages for new CAPTCHA

* fix: use 0 for total experience in SetExp packet

* feat(captcha): [wip] circle inverse filter

* fix(captcha): make expected CAPTCHA answer lowercase

* feat: use (actually legal-to-use) typewriter font

* feat: add NoiseOverlayFilter

* feat: automatically adapt the font scale

* feat(captcha): improve text positioning and coloring

* feat(captcha): add optional SmearOverlayFilter

* feat(captcha): add random glyph outlines

* fix: set debug image generation amount to 1

* refactor: use JHLab's smear filter

* style: correct some spelling mistakes in comments

* fix: let the system decide the random seed
2024-08-28 12:01:41 +02:00
Jones 769d1f2ddb Bump h2databases from 2.1.214 to 2.2.220 2024-08-27 17:36:53 +02:00
Jones f977ae4fe5 Bump mysql-connector-j from 8.4.0 to 9.0.0 2024-08-27 17:33:44 +02:00
Jones 9b11c4c1e0 Bump mariadb-java-client from 3.4.0 to 3.4.1 2024-08-27 17:30:16 +02:00
Jones 44afb98b3c Fix Bedrock false positive with rotation packets 2024-08-27 17:06:28 +02:00
Jones 1e246472cc Bump Gradle from 8.9 to 8.10 2024-08-27 14:57:48 +02:00
Jones 77ee422c64 Fix Bedrock false flagging the vehicle speed check 2024-08-27 14:04:27 +02:00
dependabot[bot] 756d7e1f89 Bump bstats from 3.0.2 to 3.0.3 (#364)
Bumps `bstats` from 3.0.2 to 3.0.3.

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

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

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

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bstats:bstats-velocity
  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-08-26 14:13:16 +02:00
Jones 2ac4b7aad3 Don't create duplicate entries in database 2024-08-24 14:49:30 +02:00
Jones 5726e2d3d2 Exempt fake (spoofed) players from being checked 2024-08-20 17:04:00 +02:00
Jones 7aea5754a9 Enable org.gradle.daemon to speed up build times 2024-08-18 16:40:45 +02:00
Jones c10adc37be Move net.minecrell.plugin-yml to version catalog 2024-08-18 16:35:50 +02:00
Jones 465d81cbc2 Release 2.1.1 (#363) 2024-08-16 14:53:10 +02:00
Michel Elkenwaat 0152e45190 Log client brand and locale if invalid 2024-08-14 19:25:37 +02:00
Michel Elkenwaat 029f7920d0 Update copyright header 2024-08-13 20:41:03 +02:00
Michel Elkenwaat 0f5184297c Fix incorrect usage of libby since https://github.com/jonesdevelopment/sonar/commit/55030c5073b845a900de444d3724790f29450f66 2024-08-12 18:42:19 +02:00
Michel Elkenwaat d15b3c86ad Bump com.google.code.gson:gson from 2.10.1 to 2.11.0 2024-08-12 18:35:44 +02:00
Michel Elkenwaat 0a26cebaa5 Use updated shadow plugin (https://github.com/GradleUp/shadow/issues/908) 2024-08-12 17:59:34 +02:00
dependabot[bot] 16cf92cd17 Bump org.jetbrains:annotations from 24.0.1 to 24.1.0 (#362)
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.0.1 to 24.1.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.0.1...24.1.0)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  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-08-12 02:33:18 +02:00
Michel Elkenwaat 2bac0458dd Use rotation packets to improve the vehicle check 2024-08-11 20:00:41 +02:00
Michel Elkenwaat de9bb44bd0 Add verification.checks.vehicle.minimum-packets to config 2024-08-11 19:44:02 +02:00
182 changed files with 1269 additions and 883 deletions

No files matched your search

+2 -2
View File
@@ -14,7 +14,7 @@ allprojects {
}
apply(plugin = "java")
apply(plugin = "io.github.goooler.shadow")
apply(plugin = "com.gradleup.shadow")
dependencies {
compileOnly(rootProject.libs.lombok)
@@ -48,8 +48,8 @@ allprojects {
relocate("com.github.benmanes.caffeine", "xyz.jonesdev.sonar.libs.caffeine")
relocate("com.mysql", "xyz.jonesdev.sonar.libs.mysql")
relocate("org.mariadb", "xyz.jonesdev.sonar.libs.mariadb")
relocate("xyz.jonesdev.capja", "xyz.jonesdev.sonar.libs.capja")
relocate("org.h2", "xyz.jonesdev.sonar.libs.h2")
relocate("com.jhlabs", "xyz.jonesdev.sonar.libs.jhlabs")
// Exclude unnecessary metadata information
exclude("META-INF/*/**")
+1 -2
View File
@@ -1,4 +1,3 @@
org.gradle.daemon=false
group=xyz.jonesdev.sonar
version=2.1.0
version=2.1.2
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+7 -4
View File
@@ -2,12 +2,15 @@
netty = "4.1.112.Final"
adventure = "4.17.0"
adventure-platform = "4.3.4"
bstats = "3.0.2"
bstats = "3.0.3"
libby = "2.0.0-SNAPSHOT"
pluginyml = "0.6.0"
[plugins]
indra-git = "net.kyori.indra.git:3.1.3"
shadow = "io.github.goooler.shadow:8.1.8"
shadow = "com.gradleup.shadow:8.3.0"
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
[libraries]
netty = { module = "io.netty:netty-all", version.ref = "netty" }
@@ -23,12 +26,12 @@ libby-velocity = { module = "com.alessiodp.libby:libby-velocity", version.ref =
bstats-bungee = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats" }
bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" }
bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" }
imagefilters = "com.jhlabs:filters:2.0.235-1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
annotations = "org.jetbrains:annotations:24.0.1"
annotations = "org.jetbrains:annotations:24.1.0"
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
lombok = "org.projectlombok:lombok:1.18.34"
capja = "xyz.jonesdev:capja:1.0.3"
bungeecord = "net.md_5:bungeecord-proxy:master-SNAPSHOT"
velocity = "com.velocitypowered:velocity-proxy:3.3.0-SNAPSHOT"
# We have to use 1.8.8 for backwards compatibility
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+1 -1
View File
@@ -1,6 +1,6 @@
rootProject.name = "Sonar"
sequenceOf("api", "bukkit", "bungee", "common", "velocity").forEach {
sequenceOf("api", "captcha", "common", "bukkit", "bungee", "velocity").forEach {
val path = "sonar-$it"
val project = ":$it"
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -191,33 +191,23 @@ public final class SonarConfiguration {
verification.gravity.maxMovementTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-movement-ticks"), 2, 100);
verification.vehicle.enabled = generalConfig.getBoolean("verification.checks.vehicle.enabled");
verification.vehicle.minimumPackets = clamp(generalConfig.getInt("verification.checks.vehicle.minimum-packets"), 0, 20);
verification.map.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.map-captcha.timing"));
verification.map.scratches = generalConfig.getBoolean("verification.checks.map-captcha.effects.scratches");
verification.map.ripple = generalConfig.getBoolean("verification.checks.map-captcha.effects.ripple");
verification.map.bump = generalConfig.getBoolean("verification.checks.map-captcha.effects.bump");
verification.map.distortion.enabled = generalConfig.getBoolean("verification.checks.map-captcha.effects.distortion.enabled");
verification.map.distortion.shape = generalConfig.getInt("verification.checks.map-captcha.effects.distortion.shape");
verification.map.distortion.distance = generalConfig.getInt("verification.checks.map-captcha.effects.distortion.distance");
verification.map.distortion.density = (float) generalConfig.getYaml().getDouble("verification.checks.map-captcha.effects.distortion.density");
verification.map.distortion.mix = (float) generalConfig.getYaml().getDouble("verification.checks.map-captcha.effects.distortion.mix");
final String backgroundImagePath = generalConfig.getString("verification.checks.map-captcha.background");
if (!backgroundImagePath.isEmpty()) {
verification.map.backgroundImage = new File(pluginFolder, backgroundImagePath);
if (!verification.map.backgroundImage.exists()) {
Sonar.get().getLogger().error("The background image does not exist! Please check the configuration.");
}
} else {
verification.map.backgroundImage = null;
}
verification.map.autoColor = generalConfig.getBoolean("verification.checks.map-captcha.auto-color");
verification.map.precomputeAmount = clamp(generalConfig.getInt("verification.checks.map-captcha.precompute"), 1, 1000);
verification.map.precomputeAmount = clamp(generalConfig.getInt("verification.checks.map-captcha.precompute"), 10, 5000);
verification.map.maxDuration = clamp(generalConfig.getInt("verification.checks.map-captcha.max-duration"), 5000, 360000);
verification.map.maxTries = generalConfig.getInt("verification.checks.map-captcha.max-tries");
verification.map.dictionary = generalConfig.getString("verification.checks.map-captcha.dictionary");
verification.map.alphabet = generalConfig.getString("verification.checks.map-captcha.alphabet");
verification.map.backgroundImage = null;
final String backgroundPath = generalConfig.getString("verification.checks.map-captcha.background");
if (!backgroundPath.isEmpty()) {
try (final InputStream inputStream = new FileInputStream(backgroundPath)) {
verification.map.backgroundImage = inputStream;
} catch (IOException exception) {
Sonar.get().getLogger().error("Could not find background image", exception);
}
}
verification.brand.enabled = generalConfig.getBoolean("verification.checks.client-brand.enabled");
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
@@ -392,26 +382,11 @@ public final class SonarConfiguration {
@Getter
public static final class Map {
private Timing timing;
private boolean scratches;
private boolean ripple;
private boolean bump;
private File backgroundImage;
private boolean autoColor;
private int precomputeAmount;
private int maxDuration;
private int maxTries;
private String dictionary;
private Smear distortion = new Smear();
@Getter
public static final class Smear {
private boolean enabled;
private int shape;
private int distance;
private float density;
private float mix;
}
private String alphabet;
private InputStream backgroundImage;
}
@Getter
@@ -425,6 +400,7 @@ public final class SonarConfiguration {
@Getter
public static final class Vehicle {
private boolean enabled;
private int minimumPackets;
}
@Getter
@@ -489,21 +465,21 @@ public final class SonarConfiguration {
Library.builder()
.groupId("com{}mysql")
.artifactId("mysql-connector-j")
.version("8.4.0")
.version("9.0.0")
.relocate("com{}mysql", "xyz{}jonesdev{}sonar{}libs{}mysql")
.build()),
MARIADB("MariaDB", "jdbc:mariadb://%s:%d/%s", new MariaDbDatabaseTypeAdapter(),
Library.builder()
.groupId("org{}mariadb{}jdbc")
.artifactId("mariadb-java-client")
.version("3.4.0")
.version("3.4.1")
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
.build()),
H2("H2", "jdbc:h2:file:%s", new H2DatabaseTypeAdapter(),
Library.builder()
.groupId("com{}h2database")
.artifactId("h2")
.version("2.1.214")
.version("2.2.220")
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
.build()),
NONE("None", null, null, null);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -249,7 +249,7 @@ public final class VerifiedPlayerController {
}
try {
dao.create(player);
dao.createIfNotExists(player);
} catch (SQLException exception) {
LOGGER.error("Error trying to add entry: {}", exception);
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import java.net.InetAddress;
@@ -45,6 +46,8 @@ public final class Fallback {
private Cache<InetAddress, Byte> blacklist;
@Setter
private long blacklistTime;
@Setter
private CaptchaGenerator captchaGenerator;
private final @NotNull FallbackQueue queue = new FallbackQueue();
private final @NotNull FallbackRatelimiter ratelimiter = new FallbackRatelimiter();
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.fallback.captcha;
import java.awt.image.BufferedImage;
@FunctionalInterface
public interface CaptchaGenerator {
BufferedImage createImage(final char[] answer);
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,8 +56,7 @@ public final class DiscordWebhook {
outputStream.write(serializedContent.getBytes(StandardCharsets.UTF_8));
}
// Warn the user if the connection might've not been successful
// I don't know why Discord doesn't respond with OK, but I guess this should be fine
// 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);
+1 -1
View File
@@ -1,7 +1,7 @@
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
plugins {
id("net.minecrell.plugin-yml.bukkit") version "0.6.0" apply true
alias(libs.plugins.pluginyml.bukkit) apply true
}
bukkit {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -36,11 +37,11 @@ import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWID
// Taken from
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/JoinListener.java
public final class BukkitJoinListener implements Listener {
private static MethodHandle handleMethod;
private static MethodHandle _handleMethod;
// ServerGamePacketListenerImpl aka. PlayerConnection
private static MethodHandle packetListenerField;
private static MethodHandle managerField;
private static MethodHandle channelField;
private static MethodHandle _listenerField;
private static MethodHandle _networkManagerField;
private static MethodHandle _channelField;
private static boolean initialized;
@@ -50,14 +51,14 @@ public final class BukkitJoinListener implements Listener {
.getDeclaredMethod("getHandle");
final Field listenerField = FallbackBukkitInjector.findField(false, handleMethod.getReturnType(),
"PlayerConnection", "ServerGamePacketListenerImpl");
final Field managerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
final Field networkManagerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
"NetworkManager", "Connection");
final Field channelField = FallbackBukkitInjector.getFieldAt(managerField.getType(), Channel.class, 0);
final Field channelField = FallbackBukkitInjector.getFieldAt(networkManagerField.getType(), Channel.class, 0);
BukkitJoinListener.handleMethod = MethodHandles.lookup().unreflect(handleMethod);
packetListenerField = MethodHandles.lookup().unreflectGetter(listenerField);
BukkitJoinListener.managerField = MethodHandles.lookup().unreflectGetter(managerField);
BukkitJoinListener.channelField = MethodHandles.lookup().unreflectGetter(channelField);
_handleMethod = MethodHandles.lookup().unreflect(handleMethod);
_listenerField = MethodHandles.lookup().unreflectGetter(listenerField);
_networkManagerField = MethodHandles.lookup().unreflectGetter(networkManagerField);
_channelField = MethodHandles.lookup().unreflectGetter(channelField);
initialized = true;
} catch (Exception exception) {
initialized = false;
@@ -66,22 +67,33 @@ public final class BukkitJoinListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST)
public void handle(final @NotNull PlayerJoinEvent event) {
// Make sure the player is not an NPC
if (!initialized || event.getPlayer().hasMetadata("NPC")) {
return;
}
try {
final Object handle = handleMethod.invoke(event.getPlayer());
final Object listener = packetListenerField.invoke(handle);
final Object networkManager = managerField.invoke(listener);
final Channel channel = (Channel) channelField.invoke(networkManager);
final Object handle = _handleMethod.invoke(event.getPlayer());
final Object listener = _listenerField.invoke(handle);
final Object networkManager = _networkManagerField.invoke(listener);
final Channel channel = (Channel) _channelField.invoke(networkManager);
// Don't continue if the channel isn't found or if the channel is inactive
if (channel == null || !channel.isActive()) {
return;
}
// Make sure the player is not a fake player
if (FakeChannelUtil.isFakePlayer(channel)) {
return;
}
// Close the channel if Sonar's handlers are not found when the PlayerJoinEvent is called
if (channel != null && channel.isActive() && channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
if (channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
channel.close();
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Failed to the channel for {}: {}", event.getPlayer().getName(), throwable);
Sonar.get().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("net.minecrell.plugin-yml.bungee") version "0.6.0" apply true
alias(libs.plugins.pluginyml.bungee) apply true
}
bungee {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+10
View File
@@ -0,0 +1,10 @@
dependencies {
compileOnly(project(":api"))
compileOnly(rootProject.libs.imagefilters)
testCompileOnly(project(":api"))
testCompileOnly(rootProject.libs.imagefilters)
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -0,0 +1,169 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha;
import com.jhlabs.image.FBMFilter;
import com.jhlabs.image.SmearFilter;
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.CurvesOverlayFilter;
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.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Random;
import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.FONTS;
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.STANDARD_FONT_SIZE;
@Getter
@RequiredArgsConstructor
public final class StandardCaptchaGenerator implements CaptchaGenerator {
private static final CurvesOverlayFilter CURVES = new CurvesOverlayFilter(3);
//private static final CircleInverseFilter CIRCLES = new CircleInverseFilter(1, 30, 10);
private static final NoiseOverlayFilter NOISE = new NoiseOverlayFilter(1, 20);
private static final SmearFilter SMEAR = new SmearFilter();
private static final FBMFilter FBM = new FBMFilter();
private static final Random RANDOM = new Random();
private static final Color[] COLORS = new Color[4];
private static final float[] COLOR_FRACTIONS = new float[COLORS.length];
static {
FBM.setAmount(0.6f);
FBM.setScale(15);
SMEAR.setShape(SmearFilter.SQUARES);
SMEAR.setDensity(0.1f);
SMEAR.setDistance(0);
SMEAR.setMix(0.35f);
// Create fractions based on the number of colors
for (int i = 0; i < COLOR_FRACTIONS.length; i++) {
COLOR_FRACTIONS[i] = (float) i / (COLOR_FRACTIONS.length - 1);
}
}
private final int width, height;
private final @Nullable InputStream background;
private BufferedImage backgroundImage;
@Override
public @NotNull BufferedImage createImage(final char[] answer) {
final BufferedImage image = createBackgroundImage();
final Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw characters and other effects on the image
applyRandomColorGradient(graphics);
drawCharacters(graphics, answer);
CURVES.transform(image, graphics);
NOISE.transform(image);
//SMEAR.filter(image, image);
//CIRCLES.transform(image); // TODO: check if the text is still easy to read after this
// Make sure to dispose the graphics instance
graphics.dispose();
return image;
}
private @NotNull BufferedImage createBackgroundImage() {
if (background == null) {
final BufferedImage image = new BufferedImage(width, height, TYPE_3BYTE_BGR);
// Fill the entire image with a noise texture
return FBM.filter(image, image);
}
try {
return backgroundImage == null ? backgroundImage = ImageIO.read(background) : backgroundImage;
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
private void applyRandomColorGradient(final @NotNull Graphics2D graphics) {
// 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);
}
// Apply the random gradient effect
graphics.setPaint(new LinearGradientPaint(0, 0, width, height,
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
}
private void drawCharacters(final @NotNull Graphics2D graphics,
final char @NotNull [] answer) {
final FontRenderContext ctx = graphics.getFontRenderContext();
final GlyphVector[] glyphs = new GlyphVector[answer.length];
for (int i = 0; i < answer.length; i++) {
// Create a glyph vector for the character with a random font
final Font font = FONTS[RANDOM.nextInt(FONTS.length)];
glyphs[i] = font.createGlyphVector(ctx, String.valueOf(answer[i]));
}
final double scalingXY = 5 - Math.min(answer.length, 5) * 0.65;
// Calculate first X and Y positions
final double totalWidth = Arrays.stream(glyphs)
.mapToDouble(glyph -> glyph.getLogicalBounds().getWidth() * scalingXY - 1)
.sum();
double beginX = Math.max(Math.min(width / 2D - totalWidth / 2D, totalWidth), 0);
double beginY = (height + STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
// Draw each glyph one by one
for (final GlyphVector glyph : glyphs) {
final AffineTransform transformation = AffineTransform.getTranslateInstance(beginX, beginY);
// Shear the glyph by a random amount
final double shearXY = Math.sin(beginX + beginY) / 6;
transformation.shear(shearXY, shearXY);
// Scale the glyph to perfectly fit the image
transformation.scale(scalingXY, scalingXY);
// Draw the glyph to the buffered image
final Shape transformedShape = transformation.createTransformedShape(glyph.getOutline());
graphics.fill(transformedShape);
// Draw a random outline around the glyph
if (RANDOM.nextFloat() < 0.25f) {
createGlyphOutline(graphics, transformedShape);
}
// Make sure the next glyph isn't drawn at the same position
beginX += glyph.getVisualBounds().getWidth() * scalingXY + 2;
beginY += -10 + RANDOM.nextFloat() * 20;
}
}
private void createGlyphOutline(final @NotNull Graphics2D graphics, final @NotNull Shape shape) {
final float txy = 1.25f + RANDOM.nextFloat();
final float width = 1 + RANDOM.nextFloat();
// Create a randomly translated and stroked shape based on the original glyph shape
final AffineTransform translation = AffineTransform.getTranslateInstance(txy, txy);
final Shape strokedShape = new BasicStroke(width).createStrokedShape(shape);
graphics.fill(translation.createTransformedShape(strokedShape));
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.InputStream;
import java.util.Objects;
@UtilityClass
class StandardTTFFontProvider {
private static final String[] FONT_NAMES = {"Kingthings_Trypewriter_2"};
static final Font[] FONTS = new Font[FONT_NAMES.length];
static final int STANDARD_FONT_SIZE = 25;
static {
for (int i = 0; i < FONT_NAMES.length; i++) {
FONTS[i] = loadFont(String.format("/assets/fonts/%s.ttf", FONT_NAMES[i]));
}
}
private static Font loadFont(final @NotNull String path) {
try (final InputStream inputStream = StandardCaptchaGenerator.class.getResourceAsStream(path)) {
// Load the font from the TTF file
final Font customFont = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(inputStream));
// Set the font size and style
return customFont.deriveFont(Font.PLAIN, STANDARD_FONT_SIZE);
} catch (Exception exception) {
throw new IllegalStateException(exception);
}
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class CircleInverseFilter {
private final int amount, minRadius, maxRadiusExpansion;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image) {
final int minX = image.getWidth() / 4;
final int minY = image.getHeight() / 4;
for (int i = 0; i < amount; i++) {
// Generate random position and radius for the circle
final float centerX = minX + RANDOM.nextInt(image.getWidth() - minX * 2);
final float centerY = minY + RANDOM.nextInt(image.getHeight() - minY * 2);
final int radius = minRadius + RANDOM.nextInt(maxRadiusExpansion);
final Circle circle = new Circle(centerX, centerY, radius);
// Draw inverted circle
_addInvertedCircle(image, circle);
}
}
private static void _addInvertedCircle(final @NotNull BufferedImage bufferedImage,
final @NotNull Circle circle) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
if (isWithinCircle(x, y, circle, circle.radius)) {
// Invert colors within the circle
final int rgb = bufferedImage.getRGB(x, y);
final int invertedRGB = invertColorAndFilter(rgb);
bufferedImage.setRGB(x, y, invertedRGB);
}
}
}
}
private static boolean isWithinCircle(final float x, final float y, final @NotNull Circle circle, int radius) {
final float distanceX = x - circle.centerX;
final float distanceY = y - circle.centerY;
return distanceX * distanceX + distanceY * distanceY <= radius * radius;
}
@Getter
@RequiredArgsConstructor
static final class Circle {
private final float centerX;
private final float centerY;
private final int radius;
}
private static int invertColorAndFilter(final int rgb) {
final Color color = new Color(rgb);
final int red = Math.max(Color.LIGHT_GRAY.getRed() - color.getRed(), 0);
final int green = Math.max(Color.LIGHT_GRAY.getGreen() - color.getGreen(), 0);
final int blue = Math.max(Color.LIGHT_GRAY.getBlue() - color.getBlue(), 0);
return new Color(red, green, blue).getRGB();
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class CurvesOverlayFilter {
private final int amount;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image,
final @NotNull Graphics2D graphics) {
// Randomize the stroke width
graphics.setStroke(new BasicStroke(1 + RANDOM.nextFloat()));
final int halfWidth = image.getWidth() / 2;
for (int i = 0; i < amount; ++i) {
final float randomX = image.getWidth() * RANDOM.nextFloat();
final float randomY = image.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;
// Control points for the cubic curve
final float ctrlX1 = randomX + sin / 2;
final float ctrlY1 = randomY - cos / 2;
final float ctrlX2 = randomX - sin / 2;
final float ctrlY2 = randomY + cos / 2;
// Draw a cubic curve instead of a straight line
graphics.draw(new CubicCurve2D.Float(x1, y1, ctrlX1, ctrlY1, ctrlX2, ctrlY2, x2, y2));
}
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class GridOverlayFilter {
private final int amount, lineWidth, randomOffset;
private static final Random RANDOM = new Random();
public void transform(final @NotNull BufferedImage image,
final @NotNull Graphics2D graphics) {
final int spacingX = image.getWidth() / amount + lineWidth;
final int spacingY = image.getHeight() / amount + lineWidth;
int currentX = spacingX / 2;
int currentY = spacingY / 2;
for (int i = 0; i < amount; i++) {
graphics.setStroke(new BasicStroke(lineWidth * 0.5f + lineWidth * RANDOM.nextFloat()));
graphics.drawLine(currentX, 0, currentX + RANDOM.nextInt(randomOffset), image.getHeight());
graphics.drawLine(0, currentY, image.getWidth(), currentY + RANDOM.nextInt(randomOffset));
currentX += spacingX + lineWidth;
currentY += spacingY + lineWidth;
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.captcha.filters;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.util.Random;
@Getter
@RequiredArgsConstructor
public final class NoiseOverlayFilter {
private final float density, amount;
private static final Random RANDOM = new Random();
private int randomGaussian(int x) {
x += (int) (RANDOM.nextGaussian() * amount);
return Math.max(Math.min(x, 0xff), 0);
}
public void transform(final @NotNull BufferedImage image) {
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
if (RANDOM.nextFloat() <= density) {
final int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
r = randomGaussian(r);
g = randomGaussian(g);
b = randomGaussian(b);
image.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
@UtilityClass
public class Main {
public void main(final String... args) throws IOException {
final Random random = new Random();
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(128, 128, null);
final long start = System.currentTimeMillis();
final char[] dictionary = {'a', 'b', 'c', 'd', 'e', 'f'/*, 'g'*/, 'h'/*, 'i'*/, 'j',
'k', 'm', 'n', 'o', 'p'/*, 'q'*/, 'r', 's', 't', 'u'/*, 'v', 'w'*/, 'x', 'y', 'z'};
// Generate image
final int amount = 1;
for (int i = 0; i < amount; i++) {
final char[] answer = new char[3 + random.nextInt(2)];
for (int j = 0; j < answer.length; j++) {
answer[j] = dictionary[random.nextInt(dictionary.length)];
}
final BufferedImage bufferedImage = standardCaptchaGenerator.createImage(answer);
// Save image
ImageIO.write(bufferedImage, "png", new File(i + ".png"));
}
System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate " + amount + " image(s)");
}
}
+1 -3
View File
@@ -1,9 +1,7 @@
dependencies {
compileOnly(project(":api"))
implementation(project(":captcha"))
compileOnly(rootProject.libs.adventure.nbt)
implementation(rootProject.libs.capja)
}
java.sourceCompatibility = JavaVersion.VERSION_11
@@ -40,12 +40,12 @@ class LibraryLoader {
Library.builder()
.groupId("com{}google{}code{}gson")
.artifactId("gson")
.version("2.10.1")
.version("2.11.0")
.relocate("com{}google{}gson", "xyz{}jonesdev{}sonar{}libs{}gson")
.build(),
// Caffeine
Library.builder()
.groupId("com.github.ben-manes.caffeine")
.groupId("com{}github{}ben-manes{}caffeine")
.artifactId("caffeine")
.version("3.1.8")
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
@@ -56,6 +56,13 @@ class LibraryLoader {
.artifactId("ormlite-jdbc")
.version("6.1")
.relocate("com{}j256{}ormlite", "xyz{}jonesdev{}sonar{}libs{}ormlite")
.build(),
// JHLabs image filters
Library.builder()
.groupId("com{}jhlabs")
.artifactId("filters")
.version("2.0.235-1")
.relocate("com{}jhlabs", "xyz{}jonesdev{}sonar{}libs{}jhlabs")
.build()
);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,6 +36,7 @@ import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.api.verbose.Notification;
import xyz.jonesdev.sonar.api.verbose.Verbose;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.service.SonarServiceManager;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
@@ -69,7 +70,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Load all libraries before anything else
LibraryLoader.loadLibraries(libraryManager, platform);
this.libraryManager = libraryManager;
// Check if Netty is up-to-date
// Check if Netty is up to date
ProtocolUtil.checkNettyVersion();
// Set the Sonar API
SonarSupplier.set(this);
@@ -81,8 +82,8 @@ public abstract class SonarBootstrap<T> implements Sonar {
this.verboseHandler = new Verbose();
this.notificationHandler = new Notification();
this.config = new SonarConfiguration(dataDirectory);
this.subcommandRegistry = new SubcommandRegistry();
// Register all subcommands
this.subcommandRegistry = new SubcommandRegistry();
this.subcommandRegistry.register(
new BlacklistCommand(),
new VerifiedCommand(),
@@ -147,6 +148,17 @@ public abstract class SonarBootstrap<T> implements Sonar {
getLogger().warn("Reloading after changing the database type is not recommended as it may cause data loss.");
}
// Update the CAPTCHA generator if necessary
if (getConfig().getVerification().getMap().getTiming() == SonarConfiguration.Verification.Timing.NEVER) {
getFallback().setCaptchaGenerator(null);
} else if (getFallback().getCaptchaGenerator() == null
|| getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
} else {
getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare cached packets
getLogger().info("Taking cached snapshots of all packets...");
FallbackPreparer.prepare();
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.Fallback;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
@@ -201,14 +199,11 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
ctx.pipeline().remove(entry.getValue());
removalListener.accept(ctx.pipeline(), entry.getKey(), entry.getValue());
}
// Add our custom pipelines
ctx.pipeline()
.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE)
.addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
Sonar.get().getConfig().getVerification().getReadTimeout(),
Sonar.get().getConfig().getVerification().getWriteTimeout(),
TimeUnit.MILLISECONDS))
.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
// Add our read/write timeout handler
ctx.pipeline().addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
Sonar.get().getConfig().getVerification().getReadTimeout(),
Sonar.get().getConfig().getVerification().getWriteTimeout(),
TimeUnit.MILLISECONDS));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,7 +30,7 @@ public final class FallbackTimeoutHandler extends IdleStateHandler {
}
@Override
protected void channelIdle(final ChannelHandlerContext ctx,
protected void channelIdle(final @NotNull ChannelHandlerContext ctx,
final @NotNull IdleStateEvent idleStateEvent) throws Exception {
// The netty (default) ReadTimeoutHandler would normally just throw an Exception
// The default ReadTimeoutHandler does only check for the boolean 'closed' and
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,8 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTailExceptionsHandler;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
@@ -118,6 +120,7 @@ public final class FallbackUserWrapper implements FallbackUser {
// Replace normal encoder to allow custom packets
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE);
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
@@ -128,6 +131,7 @@ public final class FallbackUserWrapper implements FallbackUser {
// Replace normal decoder to allow custom packets
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
pipeline.addLast(FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
// Listen for all incoming packets by setting the packet listener
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username));
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@ import xyz.jonesdev.sonar.api.ReflectiveOperationException;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -60,8 +61,9 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
throw new ReflectiveOperationException(throwable);
}
// Inject Sonar's channel handler into the pipeline
if (channel.isActive()) {
// 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.isFakePlayer(channel)) {
final ChannelHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
// We need to be careful on Bukkit, as the encoder can be different
if (Sonar.get().getPlatform() == SonarPlatform.BUKKIT) {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -454,6 +454,54 @@ public enum FallbackPacketRegistry {
map(0x0D, MINECRAFT_1_19_4, false),
map(0x0F, MINECRAFT_1_20_2, false),
map(0x12, MINECRAFT_1_21, false));
serverbound.register(SetPlayerOnGround.class, SetPlayerOnGround::new,
map(0x03, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_12, false),
map(0x0C, MINECRAFT_1_12_1, false),
map(0x0F, MINECRAFT_1_13, false),
map(0x14, MINECRAFT_1_14, false),
map(0x15, MINECRAFT_1_16, false),
map(0x14, MINECRAFT_1_17, false),
map(0x16, MINECRAFT_1_19, false),
map(0x17, MINECRAFT_1_19_1, false),
map(0x16, MINECRAFT_1_19_3, false),
map(0x17, MINECRAFT_1_19_4, false),
map(0x19, MINECRAFT_1_20_2, false),
map(0x1A, MINECRAFT_1_20_3, false),
map(0x1D, MINECRAFT_1_20_5, false));
serverbound.register(SetPlayerPositionPacket.class, SetPlayerPositionPacket::new,
map(0x04, MINECRAFT_1_7_2, false),
map(0x0C, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_12, false),
map(0x0D, MINECRAFT_1_12_1, false),
map(0x10, MINECRAFT_1_13, false),
map(0x11, MINECRAFT_1_14, false),
map(0x12, MINECRAFT_1_16, false),
map(0x11, MINECRAFT_1_17, false),
map(0x13, MINECRAFT_1_19, false),
map(0x14, MINECRAFT_1_19_1, false),
map(0x13, MINECRAFT_1_19_3, false),
map(0x14, MINECRAFT_1_19_4, false),
map(0x16, MINECRAFT_1_20_2, false),
map(0x17, MINECRAFT_1_20_3, false),
map(0x1A, MINECRAFT_1_20_5, false));
serverbound.register(SetPlayerRotationPacket.class, SetPlayerRotationPacket::new,
map(0x05, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
map(0x10, MINECRAFT_1_12, false),
map(0x0F, MINECRAFT_1_12_1, false),
map(0x12, MINECRAFT_1_13, false),
map(0x13, MINECRAFT_1_14, false),
map(0x14, MINECRAFT_1_16, false),
map(0x13, MINECRAFT_1_17, false),
map(0x15, MINECRAFT_1_19, false),
map(0x16, MINECRAFT_1_19_1, false),
map(0x15, MINECRAFT_1_19_3, false),
map(0x16, MINECRAFT_1_19_4, false),
map(0x18, MINECRAFT_1_20_2, false),
map(0x19, MINECRAFT_1_20_3, false),
map(0x1C, MINECRAFT_1_20_5, false));
serverbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
map(0x06, MINECRAFT_1_7_2, false),
map(0x0D, MINECRAFT_1_9, false),
@@ -472,38 +520,6 @@ public enum FallbackPacketRegistry {
map(0x1B, MINECRAFT_1_20_5, false));
serverbound.register(ConfirmTeleportationPacket.class, ConfirmTeleportationPacket::new,
map(0x00, MINECRAFT_1_9, false));
serverbound.register(SetPlayerPositionPacket.class, SetPlayerPositionPacket::new,
map(0x04, MINECRAFT_1_7_2, false),
map(0x0C, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_12, false),
map(0x0D, MINECRAFT_1_12_1, false),
map(0x10, MINECRAFT_1_13, false),
map(0x11, MINECRAFT_1_14, false),
map(0x12, MINECRAFT_1_16, false),
map(0x11, MINECRAFT_1_17, false),
map(0x13, MINECRAFT_1_19, false),
map(0x14, MINECRAFT_1_19_1, false),
map(0x13, MINECRAFT_1_19_3, false),
map(0x14, MINECRAFT_1_19_4, false),
map(0x16, MINECRAFT_1_20_2, false),
map(0x17, MINECRAFT_1_20_3, false),
map(0x1A, MINECRAFT_1_20_5, false));
serverbound.register(SetPlayerOnGround.class, SetPlayerOnGround::new,
map(0x03, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_12, false),
map(0x0C, MINECRAFT_1_12_1, false),
map(0x0F, MINECRAFT_1_13, false),
map(0x14, MINECRAFT_1_14, false),
map(0x15, MINECRAFT_1_16, false),
map(0x14, MINECRAFT_1_17, false),
map(0x16, MINECRAFT_1_19, false),
map(0x17, MINECRAFT_1_19_1, false),
map(0x16, MINECRAFT_1_19_3, false),
map(0x17, MINECRAFT_1_19_4, false),
map(0x19, MINECRAFT_1_20_2, false),
map(0x1A, MINECRAFT_1_20_3, false),
map(0x1D, MINECRAFT_1_20_5, false));
serverbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x0F, MINECRAFT_1_7_2, false),
map(0x05, MINECRAFT_1_9, false),
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -108,9 +108,6 @@ public class FallbackPreparer {
public double[] preparedCachedYMotions;
public double maxFallDistance;
// CAPTCHA
public final CaptchaPreparer MAP_INFO_PREPARER = new CaptchaPreparer();
// XP packets
public FallbackPacket[] xpCountdown;
@@ -198,7 +195,7 @@ public class FallbackPreparer {
// Prepare CAPTCHA messages
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter-code"),
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
incorrectCaptcha = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(
@@ -210,11 +207,11 @@ public class FallbackPreparer {
for (int i = 0; i < xpCountdown.length; i++) {
final float bar = (float) i / xpCountdown.length;
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, i));
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, 0));
}
// Prepare CAPTCHA answers
MAP_INFO_PREPARER.prepare();
CaptchaPreparer.prepare();
} else {
// Throw away if not needed
enterCodeMessage = null;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,99 +17,52 @@
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.capja.SimpleCaptchaGenerator;
import xyz.jonesdev.capja.filter.SimpleRippleFilter;
import xyz.jonesdev.capja.filter.SimpleScratchFilter;
import xyz.jonesdev.capja.libs.jhlabs.image.AbstractBufferedImageOp;
import xyz.jonesdev.capja.libs.jhlabs.image.BumpFilter;
import xyz.jonesdev.capja.libs.jhlabs.image.SmearFilter;
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
public final class CaptchaPreparer {
private static final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
private static final Random RANDOM = new Random();
@UtilityClass
public class CaptchaPreparer {
private final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
private final Random RANDOM = new Random();
private MapCaptchaInfo[] cached;
private int preparedAmount;
public void prepare() {
// Make sure we're not preparing when Sonar is already preparing answers
if (cached != null && preparedAmount != cached.length) return;
preparedAmount = 0;
if (cached != null && preparedAmount != cached.length) {
return;
}
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");
// Prepare cache
final SonarConfiguration.Verification.Map config = Sonar.get().getConfig().getVerification().getMap();
cached = new MapCaptchaInfo[config.getPrecomputeAmount()];
final int precomputeAmount = Sonar.get().getConfig().getVerification().getMap().getPrecomputeAmount();
final char[] alphabet = Sonar.get().getConfig().getVerification().getMap().getAlphabet().toCharArray();
preparedAmount = 0;
cached = new MapCaptchaInfo[precomputeAmount];
// Prepare everything asynchronously
PREPARATION_SERVICE.execute(() -> {
// Create the images using capja
final @Nullable File backgroundImage = Sonar.get().getConfig().getVerification().getMap().getBackgroundImage();
final SimpleCaptchaGenerator generator = new SimpleCaptchaGenerator(128, 128, backgroundImage);
final char[] dictionary = config.getDictionary().toCharArray();
// Prepare the filters for the CAPTCHA
final List<AbstractBufferedImageOp> filters = new ArrayList<>();
if (Sonar.get().getConfig().getVerification().getMap().isScratches()) {
filters.add(new SimpleScratchFilter(5));
}
if (Sonar.get().getConfig().getVerification().getMap().isRipple()) {
final SimpleRippleFilter rippleFilter = new SimpleRippleFilter();
rippleFilter.setXAmplitude(0);
float yAmplitude = 10 - ThreadLocalRandom.current().nextInt(20);
if (Math.abs(yAmplitude) < 3) {
yAmplitude = yAmplitude >= 0 ? 3 : -3;
}
rippleFilter.setYAmplitude(yAmplitude);
filters.add(rippleFilter);
}
if (Sonar.get().getConfig().getVerification().getMap().getDistortion().isEnabled()) {
final SmearFilter smearFilter = new SmearFilter();
smearFilter.setShape(Sonar.get().getConfig().getVerification().getMap().getDistortion().getShape());
smearFilter.setMix(Sonar.get().getConfig().getVerification().getMap().getDistortion().getMix());
smearFilter.setDensity(Sonar.get().getConfig().getVerification().getMap().getDistortion().getDensity());
smearFilter.setDistance(Sonar.get().getConfig().getVerification().getMap().getDistortion().getDistance());
filters.add(smearFilter);
}
if (Sonar.get().getConfig().getVerification().getMap().isBump()) {
filters.add(new BumpFilter());
}
for (preparedAmount = 0; preparedAmount < config.getPrecomputeAmount(); preparedAmount++) {
if (!Sonar.get().getConfig().getVerification().getMap().isAutoColor()) {
// Generate a random gradient color if automatic coloring is disabled
final Color color0 = Color.getHSBColor((float) Math.random(), 1, 1);
final Color color1 = Color.getHSBColor((float) Math.random(), 1, 0.5f);
final GradientPaint gradient = new GradientPaint(0, 0, color0,
generator.getWidth(), generator.getHeight(), color1);
generator.setGradient(gradient);
}
for (preparedAmount = 0; preparedAmount < precomputeAmount;) {
// Generate CAPTCHA answer
final char[] answer = new char[5];
final char[] answer = new char[3 + RANDOM.nextInt(2)];
for (int j = 0; j < answer.length; j++) {
answer[j] = dictionary[ThreadLocalRandom.current().nextInt(dictionary.length)];
answer[j] = alphabet[RANDOM.nextInt(alphabet.length)];
}
final BufferedImage image = generator.createImage(answer, filters);
final BufferedImage image = Sonar.get().getFallback().getCaptchaGenerator().createImage(answer);
// Convert and cache converted Minecraft map bytes
final int[] buffer = MapColorPalette.getBufferFromImage(image);
cached[preparedAmount] = new MapCaptchaInfo(new String(answer), buffer);
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), buffer);
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
@@ -117,7 +70,7 @@ public final class CaptchaPreparer {
}
public boolean isCaptchaAvailable() {
return cached != null && cached[0] != null;
return preparedAmount > 0 && cached != null && cached[0] != null;
}
public MapCaptchaInfo getRandomCaptcha() {
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,10 +26,10 @@ import java.awt.image.BufferedImage;
public class MapColorPalette {
// https://minecraft.wiki/w/Map_item_format#Full_color_tables
public static final int[] COLOR_MAP = {
-16777216,
-16777216,
-16777216,
-16777216,
-1,
-1,
-1,
-1,
-10912473,
-9594576,
-8408520,
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more