Compare commits

...
30 Commits
Author SHA1 Message Date
Jones 9bc3d73cb7 Release 2.0.31 (#308) 2024-05-25 15:10:26 +02:00
Jones d3aaba4650 Update translations (#307)
* fix: some English grammar mistakes

* fix: add missing Dutch translation string

* fix: add missing Georgian translations
2024-05-23 20:21:49 +02:00
Jones 68d99dd59b Update and improve some packet-related code (#306)
* fix: allow packet encode and decode method to throw exceptions

* fix: remove unnecessary annotations from FinishConfigurationPacket

* fix: don't modify the received plugin message channel
2024-05-23 18:12:02 +02:00
Jones 5f77f21ee9 Improve networking performance (#305)
* refactor: small structural changes

* build(deps): bump adventure-nbt from 4.16.0 to 4.17.0

* feat(velocity): use MethodHandles for better performance on connection

* feat(bungee): use MethodHandles for better performance on connection
2024-05-16 21:18:29 +02:00
Jones c5e1d12424 Fix NullPointerException (#304)
* fix: NullPointerException with VerifiedPlayerController

* fix: NullPointerException with Metrics
2024-05-16 20:15:05 +02:00
Jones 25dbbf0b1f Bump Gradle from 8.6 to 8.7 (#303) 2024-05-16 19:55:32 +02:00
Jones cbf04f6f2e Fix error in French webhook translations (#302) 2024-05-15 20:04:47 +02:00
Jones 8966d7d0e8 Add French translations (#300)
* feat: add French translations

* fix: small mistakes in some translations
2024-05-15 19:42:38 +02:00
dependabot[bot] 064a3cee2a Bump adventure-text-serializer-gson to 4.17.0 (#297)
Bumps [net.kyori:adventure-text-serializer-gson](https://github.com/KyoriPowered/adventure) from 4.16.0 to 4.17.0.
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.16.0...v4.17.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-serializer-gson
  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-05-13 13:51:56 +02:00
dependabot[bot] 260af8b2be Bump adventure-text-minimessage to 4.17.0 (#298)
Bumps [net.kyori:adventure-text-minimessage](https://github.com/KyoriPowered/adventure) from 4.16.0 to 4.17.0.
- [Release notes](https://github.com/KyoriPowered/adventure/releases)
- [Commits](https://github.com/KyoriPowered/adventure/compare/v4.16.0...v4.17.0)

---
updated-dependencies:
- dependency-name: net.kyori:adventure-text-minimessage
  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-05-13 13:50:07 +02:00
Jones a7d52467b8 Release 2.0.30 (#295) 2024-05-11 14:43:09 +02:00
Jones 0ed6b0351c Bump capja from 1.0.2 to 1.0.3 (#294) 2024-05-11 14:39:57 +02:00
Jones a152633b41 Don't cache map data to avoid memory issues (#293) 2024-05-11 14:34:26 +02:00
Jones f618d1ce77 Release 2.0.29 (#291) 2024-05-11 13:34:46 +02:00
Jones ea35904d33 Add more softdepends for compatibility (#290)
* fix(velocity): add more soft dependencies

* fix(bukkit): add more soft dependencies

* fix(bungee): add more soft dependencies
2024-05-11 13:27:37 +02:00
Jones ececb66bf1 Update translations (#289)
* feat: add missing German translations

* feat: add missing Czech translations

* feat: add missing Russian translations (thanks to @F3F5)

* fix: typo in messages/en.yml

* feat: add Dutch translations
2024-05-10 20:22:31 +02:00
Jones 6caa37e3e0 Recode the map CAPTCHA (#288)
* feat: [wip] upgrade to capja 1.0.0

* fix: don't store buffer to avoid memory issues

* fix: use one buffer array for storing RGB and indexes

* fix: color palette sometimes showing incorrect colors

* build(deps): bump capja from 1.0.0 to 1.0.1

* fix: actually let the user customize the background

* build(deps): bump capja from 1.0.1 to 1.0.2
2024-05-10 19:04:44 +02:00
Jones a057199e0b Generalize packets and clean up code (#287)
* refactor: some renaming to better understand the functionality

* refactor: port pack modified FastUUID for LoginSuccessPacket

* refactor: rename package utility to util

* fix: don't create a snapshot of FinishConfigurationPacket

* fix: don't allow encoding of LoginAcknowledgedPacket

* refactor: PlayerTickPacket -> PlayerPositionIdlePacket

* style: rename some variables for clarity

* fix: optimize UniversalChatPacket

* refactor: ChangedBlock -> BlockUpdate

* style: codestyle consistency
2024-05-05 13:45:37 +02:00
Jones d0bbcdc600 Use pre-encoded packet snapshots to reduce CPU usage (#286)
* feat: cache most packets Sonar needs to encode

* fix: make sure to also cache MapInfo packets

* fix: use NanoLimbo's hashing algorithm to consume less RAM

* fix: don't print every byte for the MapDataPacket

* feat: notify user about packet preparing
2024-05-04 21:42:36 +02:00
Jones 0a5ce856f3 Small microoptimizations (#285)
* fix: optimize experience bar and keepalive packets

* fix: optimize chat message reading

* feat: cache LoginSuccessPacket
2024-05-04 20:49:47 +02:00
Jones 261acbd9e2 Use experience bar instead of action bar (#284) 2024-05-04 20:18:34 +02:00
Jones 595b1378ab Release 2.0.28 (#282) 2024-05-03 16:59:52 +02:00
AkkariinandMichel Elkenwaat 3012ed0d7a Add experimental Geyser support (#281)
* feat: added experimental geyser support.

* feat: send a special `PlayerAbilities` packet for the bedrock client
write 0x06 (`Flying` + `Allow Flying`) to prevent the bedrock clients falling during the `map-captcha` phase.
After testing, it has been confirmed that the bedrock client is unable to attempt to de-fly status. So just add it.

* fix: if the first move are success passed predicted. then don't reset tick.

* fix: tick skipping is allowed for bedrock clients.

* feat: fix limit & only set `bedrockSpawned` when client is bedrock.

* feat: make geyser checks configurable

* style: small codestyle improvements

* style: make code more human-readable

* fix: only check first packet if it's actually the first packet

* fix: [wip] completely exclude geyser from the gravity check [ci skip]

* style: fix comment

* chore: update `zh` language file. [ci skip]

* fix: Geyser users timing out because of a stupid mistake

* fix: only skip vehicle state in position check

* feat: add missing German translations [ci skip]

* feat: add missing Czech translations [ci skip]

* fix: make sure the vehicle check is recommended [ci skip]

* style: inline some stuff for better readability

* fix(geyser): completely skip gravity check for consistency [ci skip]

* fix(geyser): skip vehicle packet order check (UDP can mess this up)

* fix: add missing translations [ci skip]

---------

Co-authored-by: Michel Elkenwaat <admin@jonesdev.xyz>
2024-05-02 14:20:21 +02:00
Akkariin 4ccecde901 Add config & messages files for Chinese Simplified (#280)
* chore: add config & messages files for Chinese Simplified.

* style: rename `cn` to `zh`. [ci skip]
2024-04-28 19:16:55 +02:00
AkkariinandMichel Elkenwaat 87fcfb68f3 1.20.5 Support (#278)
* feat: support 1.20.5

* fix: vehicle & barrier block id

* fix: missing abilities & remove entities packet id

* refactor: merge RegistryBundlePacket into RegistryDataPacket

* style: clean up some code

* feat: implement transfer packet logic for 1.20.5+

* fix: check hostname before protocolVersion

* fix: transfer configuration

* fix: SetSlotPacket for 1.20.5

* chore: remove encoder/decoder debugging message. [ci skip]

* style: remove double empty line [ci skip]

---------

Co-authored-by: Michel Elkenwaat <admin@jonesdev.xyz>
2024-04-28 12:50:17 +02:00
Jones a17b726798 Release 2.0.27 (#277) 2024-04-25 16:57:21 +02:00
Jones 50d3b43f63 Don't fail players when disconnecting (#276) 2024-04-24 15:19:29 +02:00
Jones 0ff0194894 Also check for PaddleBoat packets (#275)
* feat: also check for PaddleBoat packets

* fix: remove debug messages
2024-04-24 15:15:00 +02:00
dependabot[bot] 279fe2e3e9 Bump netty-all from 4.1.108.Final to 4.1.109.Final (#273)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.108.Final to 4.1.109.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.108.Final...netty-4.1.109.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-04-23 14:50:13 +02:00
Jones ffd3a7e5c2 Improve performance through multi-threading (#271)
* fix: optimize JVMProcessInformation#formatMemory

* fix: formatting (spacing) in formatMemory

* fix: don't re-serialize action bar format forever

* fix: optimize chat and title notifications

* feat: change task timers to run less tasks more efficiently

* feat: run all scheduled tasks asynchronously

* fix: small enable message issue

* fix(velocity): don't unnecessarily store the handshake packet

* fix: oh yes I love exclamation marks

* feat: move handshake handling logic to common module

* feat: move login handling logic to common module

* fix(bungee): decoding error caused by sending wrong original packet

* style: make FallbackChannelHandlerAdapter#initialLogin final

* fix: rearrange task priorities and improve performance by removing dead code

* fix: better debug if protocol is invalid

* fix: update default read timeout value to prevent laggy players from being blacklisted

* fix: move the unknown protocol version check to login
2024-04-23 14:49:56 +02:00
102 changed files with 3747 additions and 1428 deletions

No files matched your search

+3 -3
View File
@@ -36,12 +36,12 @@ allprojects {
testAnnotationProcessor("org.projectlombok:lombok:1.18.32")
// adventure
compileOnly("net.kyori:adventure-text-minimessage:4.16.0")
compileOnly("net.kyori:adventure-text-serializer-gson:4.16.0")
compileOnly("net.kyori:adventure-text-minimessage:4.17.0")
compileOnly("net.kyori:adventure-text-serializer-gson:4.17.0")
compileOnly("com.j256.ormlite:ormlite-jdbc:6.1") // ORMLite
compileOnly("com.github.ben-manes.caffeine:caffeine:3.1.8") // caching
compileOnly("io.netty:netty-all:4.1.108.Final") // netty
compileOnly("io.netty:netty-all:4.1.109.Final") // netty
// Library/dependency loading
compileOnly("com.alessiodp.libby:libby-core:2.0.0-SNAPSHOT")
+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev.sonar
version=2.0.26
version=2.0.31
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+9 -8
View File
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -33,7 +33,6 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.api.verbose.Notification;
import xyz.jonesdev.sonar.api.verbose.Verbose;
import java.io.File;
import java.net.InetAddress;
import java.text.DecimalFormat;
import java.util.UUID;
@@ -63,8 +62,6 @@ public interface Sonar {
*/
@NotNull LoggerWrapper getLogger();
@NotNull File getDataDirectory();
@NotNull SonarConfiguration getConfig();
@NotNull SubcommandRegistry getSubcommandRegistry();
@@ -151,10 +151,4 @@ public final class AttackTracker {
attackThreshold = 0;
}
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isCurrentlyUnderAttack() {
final int minDelay = Sonar.get().getConfig().getAttackCooldownDelay();
return currentAttack != null && currentAttack.timer.delay() < minDelay;
}
}
@@ -21,6 +21,7 @@ import com.j256.ormlite.db.DatabaseType;
import lombok.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
@@ -181,20 +182,31 @@ public final class SonarConfiguration {
verification.vehicle.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.vehicle.timing"));
verification.map.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.map-captcha.timing"));
verification.map.flare = generalConfig.getBoolean("verification.checks.map-captcha.effects.flare");
verification.map.scratches = generalConfig.getBoolean("verification.checks.map-captcha.effects.scratches");
verification.map.ripple = generalConfig.getBoolean("verification.checks.map-captcha.effects.ripple");
verification.map.smear = generalConfig.getBoolean("verification.checks.map-captcha.effects.smear");
verification.map.pinch = generalConfig.getBoolean("verification.checks.map-captcha.effects.pinch");
verification.map.saturation = (float) generalConfig.getYaml().getDouble(
"verification.checks.map-captcha.effects.saturation", 0.3);
verification.map.distortion = (float) generalConfig.getYaml().getDouble(
"verification.checks.map-captcha.effects.distortion", 2);
verification.map.precomputeAmount = generalConfig.getInt("verification.checks.map-captcha.precompute");
verification.map.maxDuration = generalConfig.getInt("verification.checks.map-captcha.max-duration");
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.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.fonts = generalConfig.getStringList("verification.checks.map-captcha.font-names");
verification.brand.enabled = generalConfig.getBoolean("verification.checks.client-brand.enabled");
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
@@ -204,6 +216,15 @@ public final class SonarConfiguration {
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
verification.maxLoginPackets = clamp(generalConfig.getInt("verification.checks.max-login-packets"), 128, 8192);
verification.transfer.enabled = generalConfig.getBoolean("verification.transfer.enabled");
verification.transfer.host = generalConfig.getString("verification.transfer.destination-host");
verification.transfer.port = clamp(generalConfig.getInt("verification.transfer.destination-port"), 0, 0xffff);
if (verification.transfer.enabled) {
Sonar.get().getLogger().info("Transferring 1.20.5+ clients is enabled. Please make sure to follow the instructions in order for this to work properly.");
}
verification.checkGeyser = generalConfig.getBoolean("verification.check-geyser-players");
verification.logConnections = generalConfig.getBoolean("verification.log-connections");
verification.logDuringAttack = generalConfig.getBoolean("verification.log-during-attack");
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions");
@@ -307,7 +328,6 @@ public final class SonarConfiguration {
verification.successLog = formatString(messagesConfig.getString("verification.logs.successful"));
verification.map.enterCode = deserialize(formatString(messagesConfig.getString("verification.captcha.enter-code")));
verification.map.enterCodeActionBar = formatString(messagesConfig.getString("verification.captcha.action-bar"));
verification.map.failedCaptcha = deserialize(formatString(messagesConfig.getString("verification.captcha.incorrect")));
verification.currentlyPreparing = deserialize(fromList(messagesConfig.getStringList("verification.currently-preparing")));
verification.tooFastReconnect = deserialize(fromList(messagesConfig.getStringList("verification.too-fast-reconnect")));
@@ -319,13 +339,16 @@ public final class SonarConfiguration {
verification.verificationSuccess = deserialize(fromList(messagesConfig.getStringList("verification.success")));
verification.verificationFailed = deserialize(fromList(messagesConfig.getStringList("verification.failed")));
verbose.actionBarLayout = formatString(messagesConfig.getString("verbose.layout.normal"));
verbose.actionBarLayoutDuringAttack = formatString(messagesConfig.getString("verbose.layout.attack"));
verbose.actionBarLayout = deserialize(formatString(messagesConfig.getString("verbose.layout.normal")));
verbose.actionBarLayoutDuringAttack = deserialize(formatString(messagesConfig.getString("verbose.layout.attack")));
verbose.animation = Collections.unmodifiableList(messagesConfig.getStringList("verbose.animation"));
notifications.notificationTitle = deserialize(formatString(messagesConfig.getString("notifications.title")));
notifications.notificationSubtitle = deserialize(formatString(messagesConfig.getString("notifications.subtitle")));
notifications.notificationChat = formatString(fromList(messagesConfig.getStringList("notifications.chat")));
notifications.title = Title.title(
Sonar.get().getConfig().getNotifications().getNotificationTitle(),
Sonar.get().getConfig().getNotifications().getNotificationSubtitle());
notifications.notificationChat = deserialize(formatString(fromList(messagesConfig.getStringList("notifications.chat"))));
}
private @NotNull URL getAsset(final String url, final @NotNull Language language) {
@@ -339,7 +362,11 @@ public final class SonarConfiguration {
}
private static int clamp(final int v, final int max, final int min) {
return Math.max(Math.min(v, min), max);
final int output = Math.max(Math.min(v, min), max);
if (output != v) {
Sonar.get().getLogger().warn("Clamped configuration value {} to {}", v, output);
}
return output;
}
public String formatAddress(final InetAddress inetAddress) {
@@ -400,17 +427,18 @@ public final class SonarConfiguration {
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static final class Verbose {
private String actionBarLayout;
private String actionBarLayoutDuringAttack;
private Component actionBarLayout;
private Component actionBarLayoutDuringAttack;
private List<String> animation;
}
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static final class Notifications {
private Title title;
private Component notificationTitle;
private Component notificationSubtitle;
private String notificationChat;
private Component notificationChat;
}
@Getter
@@ -438,25 +466,33 @@ public final class SonarConfiguration {
private final Gravity gravity = new Gravity();
private final Vehicle vehicle = new Vehicle();
private final Brand brand = new Brand();
private final Transfer transfer = new Transfer();
@Getter
public static final class Map {
private Timing timing;
private boolean flare;
private boolean scratches;
private boolean ripple;
private boolean smear;
private boolean pinch;
private float saturation;
private float distortion;
private boolean bump;
private File backgroundImage;
private boolean autoColor;
private int precomputeAmount;
private int maxDuration;
private int maxTries;
private String dictionary;
private Component enterCode;
private Component failedCaptcha;
private String enterCodeActionBar;
private List<String> fonts;
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;
}
}
@Getter
@@ -492,6 +528,14 @@ public final class SonarConfiguration {
private Pattern validRegex;
}
@Getter
public static final class Transfer {
private boolean enabled;
private String host;
private int port;
}
private boolean checkGeyser;
private boolean logConnections;
private boolean logDuringAttack;
private boolean debugXYZPositions;
@@ -83,6 +83,6 @@ public final class Fallback {
private static boolean shouldPerform(final SonarConfiguration.Verification.Timing timing) {
return timing == SonarConfiguration.Verification.Timing.ALWAYS
|| (timing == SonarConfiguration.Verification.Timing.DURING_ATTACK
&& Sonar.get().getAttackTracker().isCurrentlyUnderAttack());
&& Sonar.get().getAttackTracker().getCurrentAttack() != null);
}
}
@@ -26,15 +26,20 @@ import java.net.InetAddress;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class FallbackQueue {
static final FallbackQueue INSTANCE = new FallbackQueue();
private final Map<InetAddress, Runnable> queuedPlayers = new ConcurrentHashMap<>(64);
// Async executor for all new verifications
public static final ExecutorService QUEUE_EXECUTOR = new ForkJoinPool(
Math.min(0x7fff, Runtime.getRuntime().availableProcessors()),
ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
// Runnable task executed for polling the queue
private final Runnable pollTask = () -> {
public void poll() {
final int maxQueuePolls = Sonar.get().getConfig().getQueue().getMaxQueuePolls();
int index = 0;
@@ -47,30 +52,9 @@ public final class FallbackQueue {
}
// Run the cached runnable
final Map.Entry<InetAddress, Runnable> entry = iterator.next();
entry.getValue().run();
QUEUE_EXECUTOR.execute(entry.getValue());
// Remove runnable from iterator
iterator.remove();
}
// Run the attack check task
Sonar.get().getAttackTracker().checkIfUnderAttack();
// Clean up the cache of rate-limited IPs
Sonar.get().getFallback().getRatelimiter().cleanUpCache();
};
/**
* @param inetAddress IP address of the player
* @param runnable Queued action on the netty thread
* @see #remove
*/
public void queue(final InetAddress inetAddress, final Runnable runnable) {
queuedPlayers.put(inetAddress, runnable);
}
/**
* @param inetAddress IP address of the player
*/
public void remove(final InetAddress inetAddress) {
queuedPlayers.remove(inetAddress);
}
}
@@ -28,13 +28,19 @@ import java.net.InetAddress;
import java.util.UUID;
public interface FallbackUser {
@NotNull Channel getChannel();
@NotNull
Channel getChannel();
@NotNull InetAddress getInetAddress();
@NotNull
InetAddress getInetAddress();
@NotNull ProtocolVersion getProtocolVersion();
@NotNull
ProtocolVersion getProtocolVersion();
@NotNull FallbackUserState getState();
@NotNull
FallbackUserState getState();
boolean isGeyser();
void setState(final FallbackUserState state);
@@ -55,11 +61,11 @@ public interface FallbackUser {
* @param encoder Name of the encoder pipeline
* @param decoder Name of the decoder pipeline
* @param timeout Name of the read timeout pipeline
* @param boss Name of the main pipeline
* @param handler Name of the main pipeline
*/
void hijack(final @NotNull String username, final @NotNull UUID uuid,
final @NotNull String encoder, final @NotNull String decoder,
final @NotNull String timeout, final @NotNull String boss);
final @NotNull String timeout, final @NotNull String handler);
/**
* Sends a packet/message to the player
@@ -68,7 +68,8 @@ public enum ProtocolVersion {
MINECRAFT_1_19_4(762),
MINECRAFT_1_20(763),
MINECRAFT_1_20_2(764),
MINECRAFT_1_20_3(765);
MINECRAFT_1_20_3(765),
MINECRAFT_1_20_5(766);
private final int protocol;
@@ -21,12 +21,14 @@ import com.sun.management.OperatingSystemMXBean;
import lombok.experimental.UtilityClass;
import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
@UtilityClass
public class JVMProcessInformation {
private final Runtime RUNTIME = Runtime.getRuntime();
private final OperatingSystemMXBean MX = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
private final char[] MEMORY_UNITS = {'K', 'M', 'G', 'T', 'P', 'E'};
private final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#");
public String formatMemory(final long size) {
if (size < 1024L) {
@@ -37,7 +39,7 @@ public class JVMProcessInformation {
final double formattedSize = (double) size / (1L << (group * 10));
final char unit = MEMORY_UNITS[group - 1];
// https://en.wikipedia.org/wiki/Byte#Multiple-byte_units
return String.format("%.1f %siB", formattedSize, unit);
return DECIMAL_FORMAT.format(formattedSize) + " " + unit + "iB";
}
/**
@@ -18,13 +18,12 @@
package xyz.jonesdev.sonar.api.timer;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import java.text.SimpleDateFormat;
@Getter
public final class SystemTimer {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("mm:ss");
public static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("mm:ss");
private long start = System.currentTimeMillis();
@@ -40,19 +39,12 @@ public final class SystemTimer {
}
/**
* @apiNote Read more: {@link SystemTimer#delay}, {@link SystemTimer#formattedDelay}
* @apiNote Read more: {@link SystemTimer#delay}
*/
public boolean elapsed(final long amount) {
return delay() >= amount;
}
/**
* @apiNote Read more: {@link SystemTimer#delay}
*/
public @NotNull String formattedDelay() {
return FORMATTER.format(delay());
}
@Override
public String toString() {
return String.format("%.3f", delay() / 1000D);
@@ -20,57 +20,33 @@ package xyz.jonesdev.sonar.api.verbose;
import lombok.Getter;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
import java.util.Collection;
import java.util.UUID;
import java.util.Vector;
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
@Getter
public final class Notification implements Observable {
private final @NotNull Collection<UUID> subscribers = new Vector<>(0);
public void sendAttackNotification() {
// No need to do anything if we have no subscribers
if (subscribers.isEmpty()) return;
final SonarStatistics statistics = Sonar.get().getStatistics();
// Prepare the title
// TODO: Why do custom title times not work?
// Title.Times.times(Ticks.duration(5L), Ticks.duration(60L), Ticks.duration(10L));
final Title title = Title.title(
Sonar.get().getConfig().getNotifications().getNotificationTitle(),
Sonar.get().getConfig().getNotifications().getNotificationSubtitle());
// Prepare the chat message
final Component chat = MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getNotifications().getNotificationChat()
.replace("%queued%",
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
.replace("%blacklisted%",
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
.replace("%total-joins%", DECIMAL_FORMAT.format(statistics.getTotalPlayersJoined()))
.replace("%logins-per-second%", DECIMAL_FORMAT.format(statistics.getLoginsPerSecond()))
.replace("%connections-per-second%", DECIMAL_FORMAT.format(statistics.getConnectionsPerSecond()))
.replace("%verify-total%", DECIMAL_FORMAT.format(statistics.getTotalAttemptedVerifications()))
.replace("%verify-success%",
DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().estimatedSize()))
.replace("%verify-failed%", DECIMAL_FORMAT.format(statistics.getTotalFailedVerifications()))
.replace("%incoming-traffic%", statistics.getCurrentIncomingBandwidthFormatted())
.replace("%outgoing-traffic%", statistics.getCurrentOutgoingBandwidthFormatted())
.replace("%incoming-traffic-ttl%", statistics.getTotalIncomingBandwidthFormatted())
.replace("%outgoing-traffic-ttl%", statistics.getTotalOutgoingBandwidthFormatted()));
// Don't prepare component if there are no subscribers
Component chat = null;
// Send the title and chat messages to all online players
for (final UUID subscriber : Sonar.get().getNotificationHandler().getSubscribers()) {
final Audience audience = Sonar.get().audience(subscriber);
if (audience == null) continue;
audience.showTitle(title);
// Only prepare component if there are subscribers
if (chat == null) {
chat = replaceStatistic(Sonar.get().getConfig().getNotifications().getNotificationChat());
}
// Send the message to all online subscribers
audience.sendMessage(chat);
// Send the title to all online subscribers
audience.showTitle(Sonar.get().getConfig().getNotifications().getTitle());
}
}
}
@@ -17,11 +17,18 @@
package xyz.jonesdev.sonar.api.verbose;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
import java.util.Collection;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
public interface Observable {
Collection<UUID> getSubscribers();
@@ -46,4 +53,61 @@ public interface Observable {
default void unsubscribe(final UUID uuid) {
getSubscribers().remove(uuid);
}
// TODO: Make this a bit prettier and better for performance
default @NotNull Component replaceStatistic(final @NotNull Component component) {
final SonarStatistics statistics = Sonar.get().getStatistics();
return component
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%queued%")
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verifying%")
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%blacklisted%")
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%total-joins%")
.replacement(DECIMAL_FORMAT.format(statistics.getTotalPlayersJoined()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%logins-per-second%")
.replacement(DECIMAL_FORMAT.format(statistics.getLoginsPerSecond()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%connections-per-second%")
.replacement(DECIMAL_FORMAT.format(statistics.getConnectionsPerSecond()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-total%")
.replacement(DECIMAL_FORMAT.format(statistics.getTotalAttemptedVerifications()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-success%")
.replacement(DECIMAL_FORMAT.format(statistics.getTotalSuccessfulVerifications()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-failed%")
.replacement(DECIMAL_FORMAT.format(statistics.getTotalFailedVerifications()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%incoming-traffic%")
.replacement(statistics.getCurrentIncomingBandwidthFormatted())
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%outgoing-traffic%")
.replacement(statistics.getCurrentOutgoingBandwidthFormatted())
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%incoming-traffic-ttl%")
.replacement(statistics.getTotalIncomingBandwidthFormatted())
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%outgoing-traffic-ttl%")
.replacement(statistics.getTotalOutgoingBandwidthFormatted())
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%used-memory%")
.replacement(formatMemory(getUsedMemory()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%free-memory%")
.replacement(formatMemory(getFreeMemory()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%total-memory%")
.replacement(formatMemory(getTotalMemory()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%max-memory%")
.replacement(formatMemory(getMaxMemory()))
.build());
}
}
@@ -21,20 +21,17 @@ import lombok.Getter;
import lombok.val;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.jetbrains.annotations.ApiStatus;
import net.kyori.adventure.text.TextReplacementConfig;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.attack.AttackTracker;
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import java.util.Collection;
import java.util.UUID;
import java.util.Vector;
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
import static xyz.jonesdev.sonar.api.timer.SystemTimer.DATE_FORMATTER;
@Getter
public final class Verbose implements Observable {
@@ -43,53 +40,31 @@ public final class Verbose implements Observable {
// Run action bar verbose
public void update() {
// Make sure to clean up the cached statistics
Sonar.get().getStatistics().cleanUpCache();
// Don't prepare component if there are no subscribers
if (subscribers.isEmpty()) return;
// Prepare the action bar format component
final Component component = prepareActionBarFormat();
// Send the action bar to all online players
Component component = null;
for (final UUID subscriber : subscribers) {
final Audience audience = Sonar.get().audience(subscriber);
if (audience == null) continue;
// Only prepare component if there are subscribers
if (component == null) {
final AttackTracker.AttackStatistics attackStatistics = Sonar.get().getAttackTracker().getCurrentAttack();
final SystemTimer attackDuration = attackStatistics == null ? null : attackStatistics.getDuration();
component = replaceStatistic(attackDuration != null
? Sonar.get().getConfig().getVerbose().getActionBarLayoutDuringAttack()
: Sonar.get().getConfig().getVerbose().getActionBarLayout())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%attack-duration%")
.replacement(attackDuration == null ? "00:00" : DATE_FORMATTER.format(attackDuration.delay()))
.build())
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%animation%")
.replacement(nextAnimation())
.build());
}
// Send the action bar to all online subscribers
audience.sendActionBar(component);
}
}
@ApiStatus.Internal
public @NotNull Component prepareActionBarFormat() {
final SonarStatistics statistics = Sonar.get().getStatistics();
final AttackTracker.AttackStatistics attackStatistics = Sonar.get().getAttackTracker().getCurrentAttack();
final SystemTimer attackDuration = attackStatistics == null ? null : attackStatistics.getDuration();
return MiniMessage.miniMessage().deserialize((attackDuration != null
? Sonar.get().getConfig().getVerbose().getActionBarLayoutDuringAttack()
: Sonar.get().getConfig().getVerbose().getActionBarLayout())
.replace("%queued%",
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
.replace("%blacklisted%",
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
.replace("%blacklisted%", DECIMAL_FORMAT.format(statistics.getCurrentBlacklistSize()))
.replace("%total-joins%", DECIMAL_FORMAT.format(statistics.getTotalPlayersJoined()))
.replace("%logins-per-second%", DECIMAL_FORMAT.format(statistics.getLoginsPerSecond()))
.replace("%connections-per-second%", DECIMAL_FORMAT.format(statistics.getConnectionsPerSecond()))
.replace("%verify-total%", DECIMAL_FORMAT.format(statistics.getTotalAttemptedVerifications()))
.replace("%verify-success%",
DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().estimatedSize()))
.replace("%verify-failed%", DECIMAL_FORMAT.format(statistics.getTotalFailedVerifications()))
.replace("%attack-duration%", attackDuration == null ? "00:00" : attackDuration.formattedDelay())
.replace("%incoming-traffic%", statistics.getCurrentIncomingBandwidthFormatted())
.replace("%outgoing-traffic%", statistics.getCurrentOutgoingBandwidthFormatted())
.replace("%incoming-traffic-ttl%", statistics.getTotalIncomingBandwidthFormatted())
.replace("%outgoing-traffic-ttl%", statistics.getTotalOutgoingBandwidthFormatted())
.replace("%used-memory%", formatMemory(getUsedMemory()))
.replace("%free-memory%", formatMemory(getFreeMemory()))
.replace("%total-memory%", formatMemory(getTotalMemory()))
.replace("%max-memory%", formatMemory(getMaxMemory()))
.replace("%animation%", nextAnimation()));
}
public String nextAnimation() {
val animations = Sonar.get().getConfig().getVerbose().getAnimation();
final int nextIndex = ++animationIndex % animations.size();
+3 -6
View File
@@ -13,7 +13,8 @@ bukkit {
authors = listOf("Jones Development", "Sonar Contributors")
website = "https://jonesdev.xyz/discord/"
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
softDepend = listOf("Geyser-Spigot", "floodgate", "Protocolize", "packetevents", "ProtocolLib", "ViaVersion")
softDepend = listOf("Geyser-Spigot", "floodgate", "Protocolize", "ProtocolSupport",
"ViaVersion", "packetevents", "ProtocolLib", "FastLogin")
foliaSupported = true
commands {
@@ -36,12 +37,8 @@ dependencies {
implementation("net.kyori:adventure-platform-bukkit:4.3.2")
implementation("net.kyori:adventure-platform-api:4.3.2")
implementation("net.kyori:adventure-platform-facet:4.3.2")
// adventure minimessage
compileOnly("net.kyori:adventure-text-minimessage:4.16.0")
compileOnly("net.kyori:adventure-text-serializer-gson:4.16.0")
// adventure nbt
implementation("net.kyori:adventure-nbt:4.16.0")
implementation("net.kyori:adventure-nbt:4.17.0")
// We have to use 1.8 for backwards compatibility
compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT")
@@ -26,12 +26,10 @@ import org.bstats.charts.SimplePie;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
import java.net.InetAddress;
import java.util.Objects;
@@ -42,9 +40,8 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
public static SonarBukkit INSTANCE;
public SonarBukkit(final @NotNull SonarBukkitPlugin plugin) {
super(plugin, new BukkitLibraryManager(
plugin, plugin.getDataFolder().getName()),
plugin.getDataFolder(), SonarPlatform.BUKKIT);
super(plugin, SonarPlatform.BUKKIT, plugin.getDataFolder(),
new BukkitLibraryManager(plugin, plugin.getDataFolder().getName()));
INSTANCE = this;
}
@@ -114,23 +111,13 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
// Register Sonar command
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand());
// Register traffic service
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(),
CachedBandwidthStatistics::reset, 20L, 20L);
// Register queue service
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(),
getFallback().getQueue().getPollTask(), 10L, 10L);
// Register verbose service
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(),
Sonar.get().getVerboseHandler()::update, 4L, 4L);
}
@Override
public void disable() {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
if (metrics != null) {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
}
}
}
+2 -6
View File
@@ -9,7 +9,7 @@ bungee {
version = rootProject.version.toString().split("-")[0]
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
author = "Jones Development, Sonar Contributors"
softDepends = setOf("Geyser-BungeeCord", "floodgate", "Protocolize", "ViaVersion")
softDepends = setOf("Geyser-BungeeCord", "floodgate", "Protocolize", "ViaVersion", "packetevents", "FastLogin")
}
dependencies {
@@ -23,12 +23,8 @@ dependencies {
implementation("net.kyori:adventure-platform-bungeecord:4.3.2")
implementation("net.kyori:adventure-platform-api:4.3.2")
implementation("net.kyori:adventure-platform-facet:4.3.2")
// adventure minimessage
compileOnly("net.kyori:adventure-text-minimessage:4.16.0")
compileOnly("net.kyori:adventure-text-serializer-gson:4.16.0")
// adventure nbt
implementation("net.kyori:adventure-nbt:4.16.0")
implementation("net.kyori:adventure-nbt:4.17.0")
// Implement bStats.org for metrics
implementation("org.bstats:bstats-bungeecord:3.0.2")
@@ -26,25 +26,22 @@ import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand;
import xyz.jonesdev.sonar.bungee.fallback.FallbackInjectionHelper;
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
import java.net.InetAddress;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter
public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
public static SonarBungee INSTANCE;
public SonarBungee(final @NotNull SonarBungeePlugin plugin) {
super(plugin, new BungeeLibraryManager(plugin), plugin.getDataFolder(), SonarPlatform.BUNGEE);
super(plugin, SonarPlatform.BUNGEE, plugin.getDataFolder(), new BungeeLibraryManager(plugin));
INSTANCE = this;
}
@@ -114,25 +111,15 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
// Register Sonar command
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
// Register traffic service
getPlugin().getServer().getScheduler().schedule(getPlugin(), CachedBandwidthStatistics::reset,
1L, 1L, TimeUnit.SECONDS);
// Register queue service
getPlugin().getServer().getScheduler().schedule(getPlugin(), getFallback().getQueue().getPollTask(),
500L, 500L, TimeUnit.MILLISECONDS);
// Register verbose service
getPlugin().getServer().getScheduler().schedule(getPlugin(), Sonar.get().getVerboseHandler()::update,
200L, 200L, TimeUnit.MILLISECONDS);
// Make sure to inject into the server's connection handler
FallbackInjectionHelper.inject();
}
@Override
public void disable() {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
if (metrics != null) {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
}
}
}
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.protocol.DefinedPacket;
@@ -28,23 +27,12 @@ import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LoginRequest;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.FallbackBandwidthHandler;
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandlerAdapter;
import xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.utility.geyser.GeyserUtil;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import static net.md_5.bungee.netty.PipelineUtils.*;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter {
@@ -73,11 +61,18 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
if (wrappedPacket != null) {
// Intercept any handshake packet by the client
if (wrappedPacket instanceof Handshake) {
handleHandshake((Handshake) wrappedPacket);
final Handshake handshake = (Handshake) wrappedPacket;
handleHandshake(handshake.getHost(), handshake.getProtocolVersion());
}
// Intercept any server login packet by the client
if (wrappedPacket instanceof LoginRequest) {
handleLogin(ctx, (LoginRequest) wrappedPacket, msg);
// Make sure to use the potentially modified, real IP
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
final ChannelWrapper channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_FIELD.get(handlerBoss);
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
handleLogin(ctx, msg, loginRequest.getData(), socketAddress,
PACKET_ENCODER, PACKET_DECODER, TIMEOUT_HANDLER, BOSS_HANDLER);
return;
}
}
@@ -85,121 +80,4 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
// Make sure to let the server handle the rest
ctx.fireChannelRead(msg);
}
private void handleHandshake(final @NotNull Handshake handshake) throws Exception {
// Check if the player has already sent a handshake packet
if (protocolVersion != null) {
throw new CorruptedFrameException("Already sent handshake");
}
// Check if the hostname is invalid
if (handshake.getHost().isEmpty()) {
throw new CorruptedFrameException("Hostname is empty");
}
// Store the protocol version
protocolVersion = ProtocolVersion.fromId(handshake.getProtocolVersion());
// Connections from unknown protocol versions will be discarded
// as this is the safest way of handling unwanted connections
if (protocolVersion.isUnknown()) {
// Sonar does NOT support snapshots or unknown versions;
// I'll try my best to stay up-to-date!
throw new CorruptedFrameException("Unknown protocol");
}
// Hook the traffic listener
// TODO: Can we implement this in channelActive?
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
private void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull LoginRequest loginRequest,
final @NotNull Object wrappedMessage) throws Exception {
// Check if the player has already sent a login packet
if (username != null) {
throw new CorruptedFrameException("Already logged on");
}
// Increase joins per second for the action bar verbose
GlobalSonarStatistics.countLogin();
// Store the username
username = loginRequest.getData();
// Make sure to use the potentially modified, real IP
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
final ChannelWrapper channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_FIELD.get(handlerBoss);
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
inetAddress = socketAddress.getAddress();
// Check the blacklist here since we cannot let the player "ghost join"
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, blacklisted, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Completely skip Geyser connections
if (GeyserUtil.isGeyserConnection(channel, socketAddress)) {
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Check if the player is already queued since we don't want bots to flood the queue
if (FALLBACK.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyQueued, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Check if Fallback is already verifying a player
// → is another player with the same IP address connected to Fallback?
if (FALLBACK.getConnected().containsKey(loginRequest.getData())
|| FALLBACK.getConnected().containsValue(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying, PACKET_ENCODER, BOSS_HANDLER);
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())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Make sure we actually have to verify the player
final String offlineUUIDString = "OfflinePlayer:" + loginRequest.getData();
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Check if the IP address is currently being rate-limited
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Check if the protocol ID of the player is allowed to bypass verification
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
.contains(protocolVersion.getProtocol())) {
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Queue the connection for further processing
FALLBACK.getQueue().queue(inetAddress, () -> channel.eventLoop().execute(() -> {
// Check if the username matches the valid name regex to prevent
// UTF-16 names or other types of exploits
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
.matcher(loginRequest.getData()).matches()) {
customDisconnect(channel, protocolVersion, invalidUsername, PACKET_ENCODER, BOSS_HANDLER);
return;
}
// Create an instance for the Fallback connection
user = new FallbackUserWrapper(channel, inetAddress, protocolVersion);
// Let the verification handler take over the channel
user.hijack(loginRequest.getData(), offlineUUID, PACKET_ENCODER, PACKET_DECODER, TIMEOUT_HANDLER, BOSS_HANDLER);
}));
}
}
@@ -22,19 +22,21 @@ import io.netty.channel.ChannelInitializer;
import lombok.RequiredArgsConstructor;
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static net.md_5.bungee.netty.PipelineUtils.PACKET_DECODER;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_HANDLER;
@RequiredArgsConstructor
public final class FallbackChannelInitializer extends ChannelInitializer<Channel> {
private static final Method INIT_CHANNEL_METHOD;
private static final MethodHandle INIT_CHANNEL_METHOD;
static {
try {
INIT_CHANNEL_METHOD = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
INIT_CHANNEL_METHOD.setAccessible(true);
INIT_CHANNEL_METHOD = MethodHandles.privateLookupIn(ChannelInitializer.class, MethodHandles.lookup())
.findVirtual(ChannelInitializer.class, "initChannel", MethodType.methodType(void.class, Channel.class));
} catch (Throwable throwable) {
throw new ReflectiveOperationException(throwable);
}
@@ -46,7 +48,7 @@ public final class FallbackChannelInitializer extends ChannelInitializer<Channel
protected void initChannel(final Channel channel) throws Exception {
// Invoke the original method
try {
INIT_CHANNEL_METHOD.invoke(originalChannelInitializer, channel);
INIT_CHANNEL_METHOD.invokeExact(originalChannelInitializer, channel);
} catch (Throwable throwable) {
throw new ReflectiveOperationException(throwable);
}
+2 -2
View File
@@ -2,10 +2,10 @@ dependencies {
compileOnly(project(":api"))
// adventure nbt for packets
compileOnly("net.kyori:adventure-nbt:4.16.0")
compileOnly("net.kyori:adventure-nbt:4.17.0")
// CAPTCHA image generation
implementation("xyz.jonesdev.capja:capja:0.1.2")
implementation("xyz.jonesdev:capja:1.0.3")
}
java.sourceCompatibility = JavaVersion.VERSION_11
@@ -21,12 +21,11 @@ import com.alessiodp.libby.Library;
import com.alessiodp.libby.LibraryManager;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
@UtilityClass
class LibraryLoader {
void loadLibraries(final @NotNull LibraryManager libraryManager) {
void loadLibraries(final @NotNull LibraryManager libraryManager, final @NotNull SonarPlatform platform) {
libraryManager.addMavenCentral();
libraryManager.addJitPack();
libraryManager.loadLibraries(
@@ -74,19 +73,19 @@ class LibraryLoader {
.build()
);
// Only load adventure dependencies if needed
if (Sonar.get().getPlatform() != SonarPlatform.VELOCITY) {
// Only load adventure if not on Velocity
if (platform != SonarPlatform.VELOCITY) {
libraryManager.loadLibraries(
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-minimessage")
.version("4.16.0")
.version("4.17.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build(),
Library.builder()
.groupId("net{}kyori")
.artifactId("adventure-text-serializer-gson")
.version("4.16.0")
.version("4.17.0")
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
.build()
);
@@ -36,6 +36,7 @@ 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.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.service.SonarServiceThreadManager;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.subcommand.SubcommandRegistryHolder;
import xyz.jonesdev.sonar.common.update.UpdateChecker;
@@ -53,27 +54,22 @@ public abstract class SonarBootstrap<T> implements Sonar {
private Notification notificationHandler;
private SonarConfiguration config;
private VerifiedPlayerController verifiedPlayerController;
private File dataDirectory;
private final LibraryManager libraryManager;
private final SonarStatistics statistics;
private final SonarPlatform platform;
private final SubcommandRegistry subcommandRegistry;
private final SystemTimer launchTimer = new SystemTimer();
public SonarBootstrap(final @NotNull T plugin,
final LibraryManager libraryManager,
final File dataDirectory,
final SonarPlatform platform) {
final @NotNull SonarPlatform platform,
final @NotNull File dataDirectory,
final @NotNull LibraryManager libraryManager) {
// Load all libraries before anything else
LibraryLoader.loadLibraries(libraryManager, platform);
// Set the Sonar API
SonarSupplier.set(this);
// Store our library manager for the API
this.libraryManager = libraryManager;
// Set the plugin instance
this.plugin = plugin;
this.dataDirectory = dataDirectory;
this.platform = platform;
// Load all libraries before anything else
LibraryLoader.loadLibraries(libraryManager);
// Load the rest of the components
this.statistics = new GlobalSonarStatistics();
this.verboseHandler = new Verbose();
@@ -105,6 +101,10 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Run the per-platform initialization method
enable();
// Start threads
getLogger().info("Starting all managed threads...");
SonarServiceThreadManager.start();
// Done
getLogger().info("Done ({}s)!", launchTimer);
} catch (Throwable throwable) {
@@ -131,6 +131,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
}
// Prepare cached packets
getLogger().info("Taking cached snapshots of all packets...");
FallbackPreparer.prepare();
// Update ratelimiter caches
@@ -170,8 +171,12 @@ public abstract class SonarBootstrap<T> implements Sonar {
public final void shutdown() {
// Initialize shutdown process
getLogger().info("Starting shutdown process...");
// Close database connection
verifiedPlayerController.close();
// Interrupt threads
SonarServiceThreadManager.stop();
// Close database connection if present
if (verifiedPlayerController != null) {
verifiedPlayerController.close();
}
// Run the per-platform disable method
disable();
// Thank the user for using Sonar
@@ -20,6 +20,7 @@ package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.CorruptedFrameException;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,13 +28,18 @@ 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.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.util.GeyserDetection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.deject;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
@RequiredArgsConstructor
public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter {
@@ -63,13 +69,7 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
// The player cannot be in the queue if the IP address is invalid
if (inetAddress != null) {
// Remove the IP address from the queue
FALLBACK.getQueue().remove(inetAddress);
}
// The player cannot be in the verification if the user object doesn't exist,
// or if the user failed/succeeded the bot verification process.
if (user != null && user.getState().canReceivePackets()) {
// Mark the verification attempt as failed
user.fail("disconnected during verification");
FALLBACK.getQueue().getQueuedPlayers().remove(inetAddress);
}
// Make sure to let the server handle the rest
ctx.fireChannelInactive();
@@ -85,28 +85,161 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
ctx.close();
}
/**
* Validates and handles incoming handshake packets
*
* @param hostname Hostname (server address) sent by the client
* @param protocol Protocol version number sent by the client
*/
protected final void handleHandshake(final @NotNull String hostname, final int protocol) throws Exception {
// Check if the hostname is invalid
if (hostname.isEmpty()) {
throw new CorruptedFrameException("Hostname is empty");
}
// Check if the player has already sent a handshake packet
if (protocolVersion != null) {
throw new CorruptedFrameException("Already sent handshake");
}
// Store the protocol version
protocolVersion = ProtocolVersion.fromId(protocol);
// Hook the traffic listener
// TODO: Can we implement this in channelActive?
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
/**
* Validates and handles incoming login packets
*
* @param ctx Forwarded channel handler context
* @param loginPacket Login packet sent by the client
* @param username Username sent by the client
* @param socketAddress Socket address of the client
*/
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object loginPacket,
final @NotNull String username,
final @NotNull InetSocketAddress socketAddress,
final @NotNull String encoder,
final @NotNull String decoder,
final @NotNull String timeout,
final @NotNull String handler) throws Exception {
// Check if the player has already sent a login packet
if (this.username != null) {
throw new CorruptedFrameException("Already sent login");
}
// Connections from unknown protocol versions will be discarded
// as this is the safest way of handling unwanted connections
if (protocolVersion.isUnknown()) {
// Sonar does NOT support snapshots or unknown versions;
// I'll try my best to stay up-to-date!
throw new CorruptedFrameException("Unknown protocol version");
}
// Increase joins per second for the action bar verbose
GlobalSonarStatistics.countLogin();
// Store the username and IP address
this.username = username;
this.inetAddress = socketAddress.getAddress();
// Check the blacklist here since we cannot let the player "ghost join"
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, blacklisted, encoder, handler);
return;
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, loginPacket, encoder, handler);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserDetection.isGeyserConnection(channel, socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
initialLogin(ctx, loginPacket, encoder, handler);
return;
}
// Check if the player is already queued since we don't want bots to flood the queue
if (FALLBACK.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyQueued, encoder, handler);
return;
}
// Check if Fallback is already verifying a player
// → is another player with the same IP address connected to Fallback?
if (FALLBACK.getConnected().containsKey(username)
|| FALLBACK.getConnected().containsValue(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying, encoder, handler);
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())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted, encoder, handler);
return;
}
// Make sure we actually have to verify the player
final String offlineUUIDString = "OfflinePlayer:" + username;
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
initialLogin(ctx, loginPacket, encoder, handler);
return;
}
// Check if the IP address is currently being rate-limited
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast, encoder, handler);
return;
}
// Check if the protocol ID of the player is allowed to bypass verification
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
.contains(protocolVersion.getProtocol())) {
initialLogin(ctx, loginPacket, encoder, handler);
return;
}
// Queue the connection for further processing
FALLBACK.getQueue().getQueuedPlayers().put(inetAddress, () -> {
// Check if the username matches the valid name regex to prevent
// UTF-16 names or other types of exploits
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
.matcher(username).matches()) {
customDisconnect(channel, protocolVersion, invalidUsername, encoder, handler);
return;
}
// Create an instance for the Fallback connection
user = new FallbackUserWrapper(channel, inetAddress, protocolVersion, geyser);
// Let the verification handler take over the channel
user.hijack(username, offlineUUID, encoder, decoder, timeout, handler);
});
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*
* @param ctx Forwarded channel handler context
* @param packet Login packet sent by the client
* @param ctx Forwarded channel handler context
* @param loginPacket Login packet sent by the client
*/
protected void initialLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object packet,
final @NotNull String encoder,
final @NotNull String boss) {
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object loginPacket,
final @NotNull String encoder,
final @NotNull String handler) throws Exception {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (Sonar.get().hasTooManyAccounts(inetAddress, maxOnlinePerIp)) {
customDisconnect(channel, protocolVersion, FallbackPreparer.tooManyOnlinePerIP, encoder, boss);
customDisconnect(channel, protocolVersion, tooManyOnlinePerIP, encoder, handler);
return;
}
}
ctx.fireChannelRead(packet);
ctx.fireChannelRead(loginPacket);
// Deject the channel since we don't need it anymore
deject(channel.pipeline());
}
@@ -36,8 +36,7 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.DisconnectPacket;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
@@ -46,6 +45,9 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.CONFIG;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
@Getter
@ToString(of = {"protocolVersion", "inetAddress"})
@@ -54,16 +56,19 @@ public final class FallbackUserWrapper implements FallbackUser {
private final ChannelPipeline pipeline;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final boolean geyser;
@Setter
private FallbackUserState state = FallbackUserState.LOGIN_ACK; // 1.20.2+
public FallbackUserWrapper(final @NotNull Channel channel,
final @NotNull InetAddress inetAddress,
final @NotNull ProtocolVersion protocolVersion) {
final @NotNull ProtocolVersion protocolVersion,
final boolean geyser) {
this.channel = channel;
this.pipeline = channel.pipeline();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.geyser = geyser;
}
@Override
@@ -74,26 +79,14 @@ public final class FallbackUserWrapper implements FallbackUser {
@Override
public void hijack(final @NotNull String username, final @NotNull UUID uuid,
final @NotNull String encoder, final @NotNull String decoder,
final @NotNull String timeout, final @NotNull String boss) {
// This rarely happens when the channel hangs, but the player is still connecting
// This also fixes a unique issue with TCPShield and other reverse proxies
if (pipeline.get(encoder) == null || pipeline.get(decoder) == null) {
channel.close();
return;
}
// Remove main pipeline to completely take over the channel
if (pipeline.get(boss) != null) {
pipeline.remove(boss);
}
final @NotNull String timeout, final @NotNull String handler) {
// The player has joined the verification
GlobalSonarStatistics.totalAttemptedVerifications++;
if (Sonar.get().getConfig().getVerification().isLogConnections()) {
// Only log the processing message if the server isn't under attack.
// We let the user override this through the configuration.
if (!Sonar.get().getAttackTracker().isCurrentlyUnderAttack()
if (Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack()) {
Sonar.get().getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getConnectLog()
.replace("%name%", username)
@@ -117,16 +110,35 @@ public final class FallbackUserWrapper implements FallbackUser {
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
write(new LoginSuccessPacket(username, uuid));
channel.eventLoop().execute(() -> {
// Remove main pipeline to completely take over the channel
if (pipeline.get(handler) != null) {
pipeline.remove(handler);
}
// The LoginSuccess packet has been sent, now we can change the registry state
newEncoder.updateRegistry(protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0
? FallbackPacketRegistry.CONFIG : FallbackPacketRegistry.GAME);
// Send LoginSuccess packet to make the client think they are joining the server
write(FallbackPreparer.LOGIN_SUCCESS);
// Replace normal decoder to allow custom packets
pipeline.replace(decoder, FALLBACK_PACKET_DECODER,
new FallbackPacketDecoder(this, new FallbackVerificationHandler(this, username, uuid)));
// The LoginSuccess packet has been sent, now we can change the registry state
newEncoder.updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? CONFIG : GAME);
try {
// Prepare custom packet decoder and verification handler
final FallbackVerificationHandler verification = new FallbackVerificationHandler(this, username, uuid);
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(this, verification);
// Replace normal decoder to allow custom packets
pipeline.replace(decoder, FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
if (protocolVersion.compareTo(MINECRAFT_1_20_2) < 0) {
// Start initializing the actual join process for pre-1.20.2 clients
verification.initialJoinProcess();
}
} catch (Throwable throwable) {
// This rarely happens when the channel hangs and the player is still connecting
// I honestly have no idea how else I'm supposed to fix this
channel.close();
}
});
}
@Override
@@ -136,7 +148,7 @@ public final class FallbackUserWrapper implements FallbackUser {
// Only log the failed message if the server isn't currently under attack.
// However, we let the user override this through the configuration.
final boolean shouldLog = !Sonar.get().getAttackTracker().isCurrentlyUnderAttack()
final boolean shouldLog = Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack();
// Only disconnect the player and log if the channel is active
@@ -223,7 +235,7 @@ public final class FallbackUserWrapper implements FallbackUser {
*
* @param packet Disconnect packet
* @param encoder Encoder to replace
* @param boss Main pipeline to remove
* @param handler Main pipeline to remove
* @param channel Channel of the player
* @param protocolVersion Protocol version of the player
*/
@@ -231,7 +243,19 @@ public final class FallbackUserWrapper implements FallbackUser {
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet,
final @NotNull String encoder,
final @NotNull String boss) {
final @NotNull String handler) {
if (channel.eventLoop().inEventLoop()) {
_customDisconnect(channel, protocolVersion, packet, encoder, handler);
} else {
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet, encoder, handler));
}
}
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet,
final @NotNull String encoder,
final @NotNull String boss) {
// Remove main pipeline to completely take over the channel
if (channel.pipeline().get(boss) != null) {
channel.pipeline().remove(boss);
@@ -21,7 +21,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import lombok.val;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
@@ -32,14 +31,14 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUserState;
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.protocol.*;
import xyz.jonesdev.sonar.common.fallback.protocol.map.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.MapCaptchaInfo;
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfigurationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledgedPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import xyz.jonesdev.sonar.common.fallback.protocol.vehicle.EntityType;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.util.Objects;
import java.util.Random;
@@ -52,6 +51,8 @@ import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_
import static xyz.jonesdev.sonar.api.fallback.FallbackUserState.*;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL_LEGACY;
public final class FallbackVerificationHandler implements FallbackPacketListener {
private static final Random RANDOM = new Random();
@@ -69,15 +70,14 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
private double posX, posY, posZ, lastY, spawnYPosition;
private boolean resolvedClientBrand, resolvedClientSettings;
private boolean listenForMovements;
private boolean receivedSteerBoat, receivedPlayerInput;
// CAPTCHA
private final SystemTimer keepAlive = new SystemTimer();
private final SystemTimer actionBar = new SystemTimer();
private @Nullable MapCaptchaInfo captcha;
private int captchaTriesLeft;
private int captchaTriesLeft, lastCountdownIndex;
// Cached options to make sure the original values don't change throughout the verification
private final boolean performVehicle, performCaptcha, performGravity, performCollisions;
private final boolean performVehicle, performCaptcha, performGravity, performCollisions, transfer;
public FallbackVerificationHandler(final @NotNull FallbackUser user,
final @NotNull String username,
@@ -85,22 +85,26 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
this.user = user;
this.username = username;
this.playerUuid = playerUuid;
this.performGravity = Sonar.get().getConfig().getVerification().getGravity().isEnabled();
// We don't want to check Geyser players for valid gravity, as this might cause issues because of the protocol
this.performGravity = !user.isGeyser() && Sonar.get().getConfig().getVerification().getGravity().isEnabled();
this.performCollisions = Sonar.get().getConfig().getVerification().getGravity().isCheckCollisions();
this.performCaptcha = FALLBACK.shouldPerformCaptcha();
this.performVehicle = FALLBACK.shouldPerformVehicleCheck();
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
// Start initializing the actual join process
initialJoinProcess();
}
this.transfer = Sonar.get().getConfig().getVerification().getTransfer().isEnabled()
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0;
}
private void configure() {
// Set the state to CONFIGURE to avoid false positives
user.setState(CONFIGURE);
// Send the necessary configuration packets to the client
user.delayedWrite(REGISTRY_SYNC);
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
for (final FallbackPacket packet : REGISTRY_SYNC_1_20_5) {
user.delayedWrite(packet);
}
} else {
user.delayedWrite(REGISTRY_SYNC_LEGACY);
}
user.delayedWrite(FINISH_CONFIGURATION);
// Send all packets in one flush
user.getChannel().flush();
@@ -123,7 +127,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
}
}
private void initialJoinProcess() {
public void initialJoinProcess() {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
// 1.7 players don't have KeepAlive packets in the login process
sendJoinGamePacket();
@@ -254,38 +258,35 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Handle incoming chat messages
if (packet instanceof UniversalChatPacket) {
checkFrame(captcha != null, "Captcha not sent yet");
final UniversalChatPacket chat = (UniversalChatPacket) packet;
if (!chat.getMessage().equals(captcha.getAnswer())) {
// Captcha is incorrect
checkFrame(captchaTriesLeft-- > 0, "failed captcha too often");
user.write(incorrectCaptcha);
final String answer = Objects.requireNonNull(captcha).getAnswer();
if (chat.getMessage().equals(answer)) {
// Captcha is correct
finish();
return;
}
// Captcha is correct
finish();
// Captcha is incorrect
checkFrame(captchaTriesLeft-- > 0, "failed captcha too often");
user.write(incorrectCaptcha);
return;
}
// Every second
if (actionBar.elapsed(user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0 ? 10_000L : 1_000L)) {
final String actionBarMessage = Sonar.get().getConfig().getVerification().getMap().getEnterCodeActionBar();
// Only send action bar if the message is actually supposed to be sent
if (!actionBarMessage.isEmpty()) {
final String timeLeft = String.format("%.0f", (maxDuration - login.delay()) / 1000D);
user.write(new UniversalChatPacket(MiniMessage.miniMessage().deserialize(
actionBarMessage.replace("%time-left%", timeLeft)), UniversalChatPacket.GAME_INFO_TYPE));
// Make sure to reset the timer
actionBar.reset();
// Only expect idle (position) packets
if (packet instanceof PlayerPositionPacket
|| packet instanceof PlayerPositionLookPacket) {
// A position packet is sent every second
final long difference = maxDuration - login.delay();
final int index = (int) (difference / 1000D);
// Make sure we can actually safely get and send the packet
if (lastCountdownIndex != index && index >= 0 && xpCountdown.length > index) {
// Send the countdown using the experience bar
user.write(xpCountdown[index]);
// Send a KeepAlive packet to prevent timeout
user.write(CAPTCHA_KEEP_ALIVE);
}
}
// Every 10 seconds
if (keepAlive.elapsed(10_000L)) {
// Send a KeepAlive packet to prevent timeout
user.write(CAPTCHA_KEEP_ALIVE);
// Make sure to reset the timer
keepAlive.reset();
lastCountdownIndex = index;
}
}
@@ -307,6 +308,19 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
return;
}
if (packet instanceof PaddleBoatPacket) {
// Check if we are currently expecting a PaddleBoatPacket packet
assertState(VEHICLE);
// Only pass the player if both checks are passed
if (receivedPlayerInput && receivedSteerBoat) {
captchaOrNext();
}
// Mark paddle boat check as passed
receivedSteerBoat = true;
}
if (packet instanceof PlayerInputPacket) {
// Check if we are currently expecting a PlayerInputPacket packet
assertState(VEHICLE);
@@ -318,11 +332,23 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
checkFrame(inputPacket.getSideways() <= 0.98f, "invalid vehicle speed (s)");
// Don't verify if the player tries unmounting or jumping
// We can't fail since the player is able to jump or sneak
if (inputPacket.isJump() || inputPacket.isUnmount()) return;
checkFrame(!inputPacket.isJump(), "tried to jump in vehicle");
checkFrame(!inputPacket.isUnmount(), "tried to escape the vehicle");
// Continue checking or verify the player
captchaOrNext();
// Only pass the player if both checks are passed
if (receivedPlayerInput && receivedSteerBoat) {
captchaOrNext();
}
// pre-1.9 clients do not have a PaddleBoat packet
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
receivedSteerBoat = true;
} else if (!user.isGeyser()) {
// PaddleBoat → PlayerInput → PaddleBoat
checkFrame(receivedSteerBoat, "invalid vehicle steering order");
}
// Mark player input check as passed
receivedPlayerInput = true;
return;
}
@@ -340,8 +366,10 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
assertState(CONFIGURE);
// Check if the client has already sent valid ClientSettings and PluginMessage packets
checkFrame(resolvedClientBrand, "did not resolve client brand");
checkFrame(resolvedClientSettings, "did not resolve client settings");
if (!user.isGeyser()) {
checkFrame(resolvedClientBrand, "did not resolve client brand");
checkFrame(resolvedClientSettings, "did not resolve client settings");
}
// Start initializing the actual join process
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
@@ -372,7 +400,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
checkFrame(validateClientLocale(user, clientSettings.getLocale()), "invalid locale");
// Clients sometimes mess up the ClientSettings or PluginMessage packet.
// Probably caused by some weird race condition in Minecraft 1.13+
// Probably caused by some race condition in Minecraft 1.13+
if (user.getState() == CLIENT_SETTINGS) {
user.setState(PLUGIN_MESSAGE);
}
@@ -385,9 +413,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
if (packet instanceof PluginMessagePacket) {
final PluginMessagePacket pluginMessage = (PluginMessagePacket) packet;
final boolean usingModernChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL);
final boolean usingLegacyChannel = pluginMessage.getChannel().equals(BRAND_CHANNEL_LEGACY);
// Only the brand channel is important, drop the rest
if (pluginMessage.getChannel().equals("MC|Brand")
|| pluginMessage.getChannel().equals("minecraft:brand")) {
if (usingModernChannel || usingLegacyChannel) {
// Check if the brand packet was sent twice,
// which is not possible when using a vanilla Minecraft client.
checkFrame(!resolvedClientBrand, "duplicate client brand packet");
@@ -396,7 +426,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// system ('minecraft:' + channel) and anything below 1.13 uses
// the legacy namespace system ('MC|' + channel).
final boolean v1_13 = user.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
checkFrame(pluginMessage.getChannel().equals("MC|Brand") || v1_13, "invalid channel");
checkFrame(usingLegacyChannel || v1_13, "invalid channel");
// Validate the client branding using a regex to filter unwanted characters.
if (Sonar.get().getConfig().getVerification().getBrand().isEnabled()) {
@@ -424,7 +454,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// The transaction should always be accepted
checkFrame(transaction.isAccepted(), "transaction not accepted?!");
// Check if the transaction ID is valid
checkFrame(transaction.getId() == expectedTransactionId, "invalid transaction id");
checkFrame(transaction.getTransactionId() == expectedTransactionId, "invalid transaction id");
// First, send an Abilities packet to the client to make
// sure the player falls even in spectator mode.
@@ -453,15 +483,15 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Reset all values to ensure safety on teleport
tick = 1;
posY = lastY = -1;
expectedTeleportId = -1;
posY = lastY = expectedTeleportId = -1;
// Now we can send the chunk data
sendChunkData();
return;
}
if (user.getState() != LOGIN_ACK) {
// Only handle position packets if we aren't in certain phases
if (user.getState() != LOGIN_ACK && user.getState() != VEHICLE) {
if (packet instanceof PlayerPositionPacket) {
final PlayerPositionPacket position = (PlayerPositionPacket) packet;
handlePositionUpdate(position.getX(), position.getY(), position.getZ(), position.isOnGround());
@@ -474,8 +504,8 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
return;
}
if (packet instanceof PlayerTickPacket) {
final PlayerTickPacket player = (PlayerTickPacket) packet;
if (packet instanceof PlayerPositionIdlePacket) {
final PlayerPositionIdlePacket player = (PlayerPositionIdlePacket) packet;
handlePositionUpdate(posX, posY, posZ, player.isOnGround());
}
}
@@ -490,8 +520,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
&& x == SPAWN_X_POSITION && y == spawnYPosition && z == SPAWN_Z_POSITION) {
// Reset all values to ensure safety on teleport
tick = 1;
posY = -1;
expectedTeleportId = -1;
posY = expectedTeleportId = -1;
// Check for ground state in the first packet
checkFrame(!ground, "invalid ground state");
}
@@ -564,7 +593,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Make sure we don't run out of predicted Y motions
checkFrame(tick < preparedCachedYMotions.length, "too many movements");
if (!ground) {
if (!ground && performGravity) {
final double predictedY = preparedCachedYMotions[tick];
final double offsetY = Math.abs(deltaY - predictedY);
@@ -575,7 +604,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
}
// Check if the y motion is roughly equal to the predicted value
checkFrame(offsetY < 0.005, String.format("invalid gravity: %d, %.7f, %.10f, %.10f != %.10f",
checkFrame(offsetY < 5e-3, String.format("invalid gravity: %d, %.7f, %.10f, %.10f != %.10f",
tick, y, offsetY, deltaY, predictedY));
}
tick++;
@@ -641,7 +670,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
captchaTriesLeft = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
// Set slot to map
user.delayedWrite(new SetSlotPacket(0, 36, 1, 0,
user.delayedWrite(new SetSlotPacket(36, 1,
ItemType.FILLED_MAP.getId(user.getProtocolVersion()), SetSlotPacket.MAP_NBT));
// Send random captcha to the player
captcha = MAP_INFO_PREPARER.getRandomCaptcha();
@@ -649,7 +678,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Teleport the player to the position above the platform
user.delayedWrite(CAPTCHA_POSITION);
// Make sure the player cannot move
user.delayedWrite(CAPTCHA_ABILITIES);
user.delayedWrite(user.isGeyser() ? CAPTCHA_ABILITIES_BEDROCK : CAPTCHA_ABILITIES);
// Make sure the player knows what to do
user.delayedWrite(enterCodeMessage);
// Send all packets in one flush
@@ -671,6 +700,13 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Call the VerifySuccessEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(username, playerUuid, user, login.delay()));
// If enabled, transfer the player back to the origin server
// This feature was introduced by Mojang in Minecraft version 1.20.5
// Thanks a lot to @Kadeluxe for helping me out with this!
if (transfer) {
user.write(transferToOrigin);
}
// Disconnect player with the verification success message
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
@@ -20,7 +20,10 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
import io.netty.buffer.ByteBuf;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.UUID;
public interface FallbackPacket {
UUID EMPTY_UUID = new UUID(0L, 0L);
/**
* Encodes the packet sent by the server
@@ -28,7 +28,7 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
private final FallbackUser user;
@@ -101,7 +101,7 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
}
}
private void doLengthSanityChecks(final ByteBuf byteBuf,
private void doLengthSanityChecks(final @NotNull ByteBuf byteBuf,
final @NotNull FallbackPacket packet) throws Exception {
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, user.getProtocolVersion());
if (expectedMaxLen != -1 && byteBuf.readableBytes() > expectedMaxLen) {
@@ -25,7 +25,7 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.CLIENTBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPacket> {
private final ProtocolVersion protocolVersion;
@@ -42,10 +42,12 @@ public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPa
@Override
protected void encode(final ChannelHandlerContext ctx,
final FallbackPacket msg,
final @NotNull FallbackPacket packet,
final ByteBuf out) throws Exception {
final int packetId = registry.getPacketId(msg);
final FallbackPacket originalPacket = packet instanceof FallbackPacketSnapshot
? ((FallbackPacketSnapshot) packet).getOriginalPacket() : packet;
final int packetId = registry.getPacketId(originalPacket);
writeVarInt(out, packetId);
msg.encode(out, protocolVersion);
packet.encode(out, protocolVersion);
}
}
@@ -33,7 +33,7 @@ import java.util.function.Supplier;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// Most of this is taken from
// Mostly taken from
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
public enum FallbackPacketRegistry {
LOGIN {
@@ -51,22 +51,30 @@ public enum FallbackPacketRegistry {
CONFIG {
{
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x01, MINECRAFT_1_20_2, true));
map(0x01, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_20_5, true));
clientbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
map(0x02, MINECRAFT_1_20_2, false));
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false));
map(0x03, MINECRAFT_1_20_2, false),
map(0x04, MINECRAFT_1_20_5, false));
clientbound.register(RegistryDataPacket.class, RegistryDataPacket::new,
map(0x05, MINECRAFT_1_20_2, true));
map(0x05, MINECRAFT_1_20_2, true),
map(0x07, MINECRAFT_1_20_5, true));
serverbound.register(ClientSettingsPacket.class, ClientSettingsPacket::new,
map(0x00, MINECRAFT_1_20_2, false));
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false));
map(0x01, MINECRAFT_1_20_2, false),
map(0x02, MINECRAFT_1_20_5, false));
serverbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
map(0x02, MINECRAFT_1_20_2, false));
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false));
map(0x03, MINECRAFT_1_20_2, false),
map(0x04, MINECRAFT_1_20_5, false));
}
},
GAME {
@@ -84,7 +92,8 @@ public enum FallbackPacketRegistry {
map(0x25, MINECRAFT_1_19_1, true),
map(0x24, MINECRAFT_1_19_3, true),
map(0x28, MINECRAFT_1_19_4, true),
map(0x29, MINECRAFT_1_20_2, true));
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
@@ -98,7 +107,8 @@ public enum FallbackPacketRegistry {
map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false),
map(0x23, MINECRAFT_1_19_4, false),
map(0x24, MINECRAFT_1_20_2, false));
map(0x24, MINECRAFT_1_20_2, false),
map(0x26, MINECRAFT_1_20_5, false));
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
map(0x1A, MINECRAFT_1_9, true),
@@ -112,7 +122,8 @@ public enum FallbackPacketRegistry {
map(0x19, MINECRAFT_1_19_1, true),
map(0x17, MINECRAFT_1_19_3, true),
map(0x1A, MINECRAFT_1_19_4, true),
map(0x1B, MINECRAFT_1_20_2, true));
map(0x1B, MINECRAFT_1_20_2, true),
map(0x1D, MINECRAFT_1_20_5, true));
clientbound.register(PlayerPositionLookPacket.class, PlayerPositionLookPacket::new,
map(0x08, MINECRAFT_1_7_2, true),
map(0x2E, MINECRAFT_1_9, true),
@@ -127,7 +138,8 @@ public enum FallbackPacketRegistry {
map(0x39, MINECRAFT_1_19_1, true),
map(0x38, MINECRAFT_1_19_3, true),
map(0x3C, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_20_2, true));
map(0x3E, MINECRAFT_1_20_2, true),
map(0x40, MINECRAFT_1_20_5, true));
clientbound.register(ClientAbilitiesPacket.class, ClientAbilitiesPacket::new,
map(0x39, MINECRAFT_1_7_2, true),
map(0x2B, MINECRAFT_1_9, true),
@@ -142,7 +154,8 @@ public enum FallbackPacketRegistry {
map(0x31, MINECRAFT_1_19_1, true),
map(0x30, MINECRAFT_1_19_3, true),
map(0x34, MINECRAFT_1_19_4, true),
map(0x36, MINECRAFT_1_20_2, true));
map(0x36, MINECRAFT_1_20_2, true),
map(0x38, MINECRAFT_1_20_5, true));
clientbound.register(ChunkDataPacket.class, ChunkDataPacket::new,
map(0x21, MINECRAFT_1_7_2, true),
map(0x20, MINECRAFT_1_9, true),
@@ -156,7 +169,8 @@ public enum FallbackPacketRegistry {
map(0x21, MINECRAFT_1_19_1, true),
map(0x20, MINECRAFT_1_19_3, true),
map(0x24, MINECRAFT_1_19_4, true),
map(0x25, MINECRAFT_1_20_2, true));
map(0x25, MINECRAFT_1_20_2, true),
map(0x27, MINECRAFT_1_20_5, true));
clientbound.register(RemoveEntitiesPacket.class, RemoveEntitiesPacket::new,
map(0x13, MINECRAFT_1_7_2, true),
map(0x30, MINECRAFT_1_9, true),
@@ -173,7 +187,8 @@ public enum FallbackPacketRegistry {
map(0x3A, MINECRAFT_1_19_3, true),
map(0x3E, MINECRAFT_1_19_4, true),
map(0x3E, MINECRAFT_1_19_4, true),
map(0x40, MINECRAFT_1_20_2, true));
map(0x40, MINECRAFT_1_20_2, true),
map(0x42, MINECRAFT_1_20_5, true));
clientbound.register(SetPassengersPacket.class, SetPassengersPacket::new,
map(0x1B, MINECRAFT_1_7_2, true),
map(0x40, MINECRAFT_1_9, true),
@@ -187,7 +202,8 @@ public enum FallbackPacketRegistry {
map(0x55, MINECRAFT_1_19_3, true),
map(0x59, MINECRAFT_1_19_4, true),
map(0x5B, MINECRAFT_1_20_2, true),
map(0x5D, MINECRAFT_1_20_3, true));
map(0x5D, MINECRAFT_1_20_3, true),
map(0x5F, MINECRAFT_1_20_5, true));
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
@@ -205,7 +221,8 @@ public enum FallbackPacketRegistry {
map(0x3F, MINECRAFT_1_19_3, true),
map(0x43, MINECRAFT_1_19_4, true),
map(0x45, MINECRAFT_1_20_2, true),
map(0x47, MINECRAFT_1_20_3, true));
map(0x47, MINECRAFT_1_20_3, true),
map(0x49, MINECRAFT_1_20_5, true));
clientbound.register(TransactionPacket.class, TransactionPacket::new,
map(0x32, MINECRAFT_1_7_2, true),
map(0x11, MINECRAFT_1_9, true),
@@ -218,7 +235,8 @@ public enum FallbackPacketRegistry {
map(0x2F, MINECRAFT_1_19_1, true),
map(0x2E, MINECRAFT_1_19_3, true),
map(0x32, MINECRAFT_1_19_4, true),
map(0x33, MINECRAFT_1_20_2, true));
map(0x33, MINECRAFT_1_20_2, true),
map(0x35, MINECRAFT_1_20_5, true));
clientbound.register(DefaultSpawnPositionPacket.class, DefaultSpawnPositionPacket::new,
map(0x05, MINECRAFT_1_7_2, true),
map(0x43, MINECRAFT_1_9, true),
@@ -234,7 +252,8 @@ public enum FallbackPacketRegistry {
map(0x4C, MINECRAFT_1_19_3, true),
map(0x50, MINECRAFT_1_19_4, true),
map(0x52, MINECRAFT_1_20_2, true),
map(0x54, MINECRAFT_1_20_3, true));
map(0x54, MINECRAFT_1_20_3, true),
map(0x56, MINECRAFT_1_20_5, true));
clientbound.register(MapDataPacket.class, MapDataPacket::new,
map(0x34, MINECRAFT_1_7_2, true),
map(0x24, MINECRAFT_1_9, true),
@@ -247,7 +266,8 @@ public enum FallbackPacketRegistry {
map(0x26, MINECRAFT_1_19_1, true),
map(0x25, MINECRAFT_1_19_3, true),
map(0x29, MINECRAFT_1_19_4, true),
map(0x2A, MINECRAFT_1_20_2, true));
map(0x2A, MINECRAFT_1_20_2, true),
map(0x2C, MINECRAFT_1_20_5, true));
clientbound.register(SetSlotPacket.class, SetSlotPacket::new,
map(0x2F, MINECRAFT_1_7_2, true),
map(0x16, MINECRAFT_1_9, true),
@@ -261,6 +281,21 @@ public enum FallbackPacketRegistry {
map(0x12, MINECRAFT_1_19_3, true),
map(0x14, MINECRAFT_1_19_4, true),
map(0x15, MINECRAFT_1_20_2, true));
clientbound.register(SetExperiencePacket.class, SetExperiencePacket::new,
map(0x1F, MINECRAFT_1_7_2, true),
map(0x3D, MINECRAFT_1_9, true),
map(0x3F, MINECRAFT_1_12, true),
map(0x40, MINECRAFT_1_12_1, true),
map(0x43, MINECRAFT_1_13, true),
map(0x47, MINECRAFT_1_14, true),
map(0x48, MINECRAFT_1_15, true),
map(0x51, MINECRAFT_1_17, true),
map(0x54, MINECRAFT_1_19_1, true),
map(0x52, MINECRAFT_1_19_3, true),
map(0x56, MINECRAFT_1_19_4, true),
map(0x58, MINECRAFT_1_20_2, true),
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true));
clientbound.register(UniversalChatPacket.class, UniversalChatPacket::new,
map(0x02, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
@@ -273,10 +308,29 @@ public enum FallbackPacketRegistry {
map(0x60, MINECRAFT_1_19_3, false),
map(0x64, MINECRAFT_1_19_4, false),
map(0x67, MINECRAFT_1_20_2, false),
map(0x69, MINECRAFT_1_20_3, false));
map(0x69, MINECRAFT_1_20_3, false),
map(0x6C, MINECRAFT_1_20_5, false));
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true));
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true));
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, true));
serverbound.register(PaddleBoatPacket.class, PaddleBoatPacket::new,
map(0x11, MINECRAFT_1_9, false),
map(0x12, MINECRAFT_1_12, false),
map(0x11, MINECRAFT_1_12_1, false),
map(0x14, MINECRAFT_1_13, false),
map(0x16, MINECRAFT_1_14, false),
map(0x17, MINECRAFT_1_16, false),
map(0x16, MINECRAFT_1_17, false),
map(0x18, MINECRAFT_1_19, false),
map(0x19, MINECRAFT_1_19_1, false),
map(0x18, MINECRAFT_1_19_3, false),
map(0x19, MINECRAFT_1_19_4, false),
map(0x1B, MINECRAFT_1_20_2, false),
map(0x1C, MINECRAFT_1_20_3, false),
map(0x1F, MINECRAFT_1_20_5, false));
serverbound.register(PlayerInputPacket.class, PlayerInputPacket::new,
map(0x0C, MINECRAFT_1_7_2, false),
map(0x15, MINECRAFT_1_9, false),
@@ -290,7 +344,8 @@ public enum FallbackPacketRegistry {
map(0x1E, MINECRAFT_1_19_3, false),
map(0x1F, MINECRAFT_1_19_4, false),
map(0x22, MINECRAFT_1_20_2, false),
map(0x23, MINECRAFT_1_20_3, false));
map(0x23, MINECRAFT_1_20_3, false),
map(0x26, MINECRAFT_1_20_5, false));
serverbound.register(UniversalChatPacket.class, UniversalChatPacket::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
@@ -299,7 +354,8 @@ public enum FallbackPacketRegistry {
map(0x03, MINECRAFT_1_14, false),
map(0x04, MINECRAFT_1_19, false),
map(0x05, MINECRAFT_1_19_1, false),
map(0x05, MINECRAFT_1_19_3, false));
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
@@ -314,7 +370,8 @@ public enum FallbackPacketRegistry {
map(0x11, MINECRAFT_1_19_3, false),
map(0x12, MINECRAFT_1_19_4, false),
map(0x14, MINECRAFT_1_20_2, false),
map(0x15, MINECRAFT_1_20_3, false));
map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false));
serverbound.register(ClientSettingsPacket.class, ClientSettingsPacket::new,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
@@ -325,7 +382,8 @@ public enum FallbackPacketRegistry {
map(0x08, MINECRAFT_1_19_1, false),
map(0x07, MINECRAFT_1_19_3, false),
map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false));
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false));
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
@@ -353,7 +411,8 @@ public enum FallbackPacketRegistry {
map(0x14, MINECRAFT_1_19_3, false),
map(0x15, MINECRAFT_1_19_4, false),
map(0x17, MINECRAFT_1_20_2, false),
map(0x18, MINECRAFT_1_20_3, false));
map(0x18, MINECRAFT_1_20_3, false),
map(0x1B, MINECRAFT_1_20_5, false));
serverbound.register(TeleportConfirmPacket.class, TeleportConfirmPacket::new,
map(0x00, MINECRAFT_1_9, false));
serverbound.register(PlayerPositionPacket.class, PlayerPositionPacket::new,
@@ -370,8 +429,9 @@ public enum FallbackPacketRegistry {
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));
serverbound.register(PlayerTickPacket.class, PlayerTickPacket::new,
map(0x17, MINECRAFT_1_20_3, false),
map(0x1A, MINECRAFT_1_20_5, false));
serverbound.register(PlayerPositionIdlePacket.class, PlayerPositionIdlePacket::new,
map(0x03, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_12, false),
@@ -385,7 +445,8 @@ public enum FallbackPacketRegistry {
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(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),
@@ -399,7 +460,8 @@ public enum FallbackPacketRegistry {
map(0x1F, MINECRAFT_1_19_3, false),
map(0x20, MINECRAFT_1_19_4, false),
map(0x23, MINECRAFT_1_20_2, false),
map(0x24, MINECRAFT_1_20_3, false));
map(0x24, MINECRAFT_1_20_3, false),
map(0x27, MINECRAFT_1_20_5, false));
}
};
@@ -0,0 +1,93 @@
/*
* 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.common.fallback.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.ID_TO_PROTOCOL_CONSTANT;
// Mostly taken from
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/PacketSnapshot.java
@Getter
public final class FallbackPacketSnapshot implements FallbackPacket {
private final Map<Integer, Integer> mappings = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
private final Map<Integer, byte[]> cachedBytes = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
private final FallbackPacket originalPacket;
public FallbackPacketSnapshot(final @NotNull FallbackPacket originalPacket) {
final Map<Integer, Integer> hashes = new HashMap<>(ID_TO_PROTOCOL_CONSTANT.size());
for (final ProtocolVersion protocolVersion : ID_TO_PROTOCOL_CONSTANT.values()) {
// Allocate a buffer for each protocol version
final ByteBuf byteBuf = Unpooled.buffer();
try {
originalPacket.encode(byteBuf, protocolVersion);
} catch (Exception exception) {
Sonar.get().getLogger().error("Could not encode packet {} for version {}: {}",
originalPacket.toString(), protocolVersion, exception);
break;
}
// Make sure we don't unnecessarily fill the RAM
final int protocol = protocolVersion.getProtocol();
final int hash = byteBuf.hashCode();
final int hashed = hashes.getOrDefault(hash, -1);
if (hashed != -1) {
mappings.put(protocol, hashed);
} else {
hashes.put(hash, protocol);
mappings.put(protocol, protocol);
// Cache the raw bytes of the encoded packet
final byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
cachedBytes.put(protocol, bytes);
}
// Make sure to release the buffer to avoid memory leaks
byteBuf.release();
}
this.originalPacket = originalPacket;
}
@Override
public void encode(final @NotNull ByteBuf byteBuf,
final @NotNull ProtocolVersion protocolVersion) throws Exception {
final int hash = mappings.get(protocolVersion.getProtocol());
final byte[] bytes = cachedBytes.get(hash);
if (bytes != null) {
byteBuf.writeBytes(bytes);
} else {
Sonar.get().getLogger().error("Could not find cached packet {} for version {}",
toString(), protocolVersion);
throw new IllegalStateException("Unable to find cached packet. Contact the developer!");
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
throw new UnsupportedOperationException();
}
}
@@ -20,32 +20,38 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockPosition;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.ChangedBlock;
import xyz.jonesdev.sonar.common.fallback.protocol.map.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfigurationPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistryDataPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@UtilityClass
public class FallbackPreparer {
// LoginSuccess
public final FallbackPacket LOGIN_SUCCESS = new FallbackPacketSnapshot(new LoginSuccessPacket(new UUID(1L, 1L), "Sonar"));
// Abilities
public final FallbackPacket DEFAULT_ABILITIES = new ClientAbilitiesPacket(0x00, 0f, 0f);
public final FallbackPacket CAPTCHA_ABILITIES = new ClientAbilitiesPacket(0x02, 0f, 0f);
public final FallbackPacket DEFAULT_ABILITIES = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x00, 0f, 0f));
public final FallbackPacket CAPTCHA_ABILITIES = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x02, 0f, 0f));
public final FallbackPacket CAPTCHA_ABILITIES_BEDROCK = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x06, 0f, 0f));
// Chunks
public final FallbackPacket EMPTY_CHUNK_DATA = new ChunkDataPacket(0, 0);
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
// Finish Configuration
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket();
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfigurationPacket(); // No snapshot needed because it's empty either way
// Synchronize Registry
public final FallbackPacket REGISTRY_SYNC = new RegistryDataPacket();
public final FallbackPacket REGISTRY_SYNC_LEGACY = new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null));
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
// Keep Alive
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlivePacket(0L);
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(-1337L));
// Game Event (1.20.3+)
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final FallbackPacket START_WRITING_CHUNKS = new FallbackPacketSnapshot(new GameEventPacket(13, 0));
// Chat
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
@@ -56,6 +62,8 @@ public class FallbackPreparer {
public FallbackPacket updateSectionBlocks;
// Default Spawn Position
public FallbackPacket dynamicSpawnPosition;
// Transfer packet
public FallbackPacket transferToOrigin;
// Disconnect messages
public FallbackPacket blacklisted;
@@ -78,13 +86,13 @@ public class FallbackPreparer {
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is the maximum Y position allowed
// Captcha position
public final FallbackPacket CAPTCHA_POSITION = new PlayerPositionLookPacket(
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION, 0f, 90f, 0, false);
public final FallbackPacket CAPTCHA_SPAWN_POSITION = new DefaultSpawnPositionPacket(
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION);
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new PlayerPositionLookPacket(
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION, 0f, 90f, 0, false));
public final FallbackPacket CAPTCHA_SPAWN_POSITION = new FallbackPacketSnapshot(new DefaultSpawnPositionPacket(
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION));
// Blocks
private final ChangedBlock[] CHANGED_BLOCKS = new ChangedBlock[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
private final BlockUpdate[] CHANGED_BLOCKS = new BlockUpdate[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
public BlockType blockType = BlockType.BARRIER;
public int maxMovementTick, dynamicSpawnYPosition;
@@ -94,13 +102,18 @@ public class FallbackPreparer {
// CAPTCHA
public final CaptchaPreparer MAP_INFO_PREPARER = new CaptchaPreparer();
// XP packets
public FallbackPacket[] xpCountdown;
public void prepare() {
// Prepare JoinGame packet
joinGame = new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGravity().getGamemode().getId(),
0, false, 0,
true, false, false,
new String[]{"minecraft:overworld"}, "minecraft:overworld");
// Prepare cached motions for the gravity check
maxFallDistance = 0;
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
preparedCachedYMotions = new double[maxMovementTick + 8];
@@ -113,45 +126,61 @@ public class FallbackPreparer {
// Set the dynamic block and collide Y position based on the maximum fall distance
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + (int) Math.ceil(maxFallDistance);
dynamicSpawnPosition = new DefaultSpawnPositionPacket(SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION);
dynamicSpawnPosition = new FallbackPacketSnapshot(new DefaultSpawnPositionPacket(
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION));
// Prepare collision platform positions
int index = 0;
for (int x = 0; x < BLOCKS_PER_ROW; x++) {
for (int z = 0; z < BLOCKS_PER_ROW; z++) {
final BlockPosition position = new BlockPosition(
final BlockUpdate.BlockPosition position = new BlockUpdate.BlockPosition(
x + (BLOCKS_PER_ROW / 2),
DEFAULT_Y_COLLIDE_POSITION,
z + (BLOCKS_PER_ROW / 2),
0, 0);
CHANGED_BLOCKS[index++] = new ChangedBlock(position, blockType);
CHANGED_BLOCKS[index++] = new BlockUpdate(position, blockType);
}
}
// Prepare UpdateSectionBlocks packet
updateSectionBlocks = new UpdateSectionBlocksPacket(0, 0, CHANGED_BLOCKS);
updateSectionBlocks = new FallbackPacketSnapshot(new UpdateSectionBlocksPacket(0, 0, CHANGED_BLOCKS));
// Prepare disconnect packets during login
blacklisted = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getBlacklisted(), true);
alreadyVerifying = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true);
alreadyQueued = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true);
protocolBlacklisted = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true);
reconnectedTooFast = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true);
invalidUsername = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true);
tooManyOnlinePerIP = DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true);
blacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getBlacklisted(), true));
alreadyVerifying = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyVerifying(), true));
alreadyQueued = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getAlreadyQueued(), true));
protocolBlacklisted = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true));
reconnectedTooFast = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true));
invalidUsername = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true));
tooManyOnlinePerIP = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
// Precompute captcha answers
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
enterCodeMessage = new UniversalChatPacket(Sonar.get().getConfig().getVerification().getMap().getEnterCode(), UniversalChatPacket.SYSTEM_TYPE);
incorrectCaptcha = new UniversalChatPacket(
Sonar.get().getConfig().getVerification().getMap().getFailedCaptcha(), UniversalChatPacket.SYSTEM_TYPE);
MAP_INFO_PREPARER.prepare();
}
// Prepare transfer packet
transferToOrigin = new FallbackPacketSnapshot(new TransferPacket(
Sonar.get().getConfig().getVerification().getTransfer().getHost(),
Sonar.get().getConfig().getVerification().getTransfer().getPort()));
// Prepare packets for the vehicle check
removeEntities = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
setPassengers = new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID);
removeEntities = new FallbackPacketSnapshot(new RemoveEntitiesPacket(VEHICLE_ENTITY_ID));
setPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
// Prepare CAPTCHA messages
enterCodeMessage = new FallbackPacketSnapshot(new UniversalChatPacket(
Sonar.get().getConfig().getVerification().getMap().getEnterCode(), UniversalChatPacket.SYSTEM_TYPE));
incorrectCaptcha = new FallbackPacketSnapshot(new UniversalChatPacket(
Sonar.get().getConfig().getVerification().getMap().getFailedCaptcha(), UniversalChatPacket.SYSTEM_TYPE));
// Prepare countdown
xpCountdown = new FallbackPacket[Sonar.get().getConfig().getVerification().getMap().getMaxDuration() / 1000];
for (int i = 0; i < xpCountdown.length; i++) {
final float bar = (float) i / xpCountdown.length;
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, i));
}
// Prepare CAPTCHA answers
MAP_INFO_PREPARER.prepare();
}
}
}
@@ -78,6 +78,7 @@ public enum BlockType {
return 10225;
case MINECRAFT_1_20_2:
case MINECRAFT_1_20_3:
case MINECRAFT_1_20_5:
return 10366;
}
}, 1D);
@@ -18,15 +18,17 @@
package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
@Getter
public final class ChangedBlock {
public final class BlockUpdate {
private final @NotNull BlockPosition position;
private final BlockType type;
private final int legacyBlockState, blockState;
public ChangedBlock(final @NotNull BlockPosition position, final BlockType type) {
public BlockUpdate(final @NotNull BlockPosition position, final BlockType type) {
this.position = position;
this.type = type;
final int x = position.getX() - (position.getChunkX() << 4);
@@ -35,4 +37,12 @@ public final class ChangedBlock {
this.legacyBlockState = x << 12 | z << 8 | y;
this.blockState = x << 8 | z << 4 | y - ((y >> 4) << 4);
}
@Getter
@ToString
@RequiredArgsConstructor
public static final class BlockPosition {
private final int x, y, z;
private final int chunkX, chunkZ;
}
}
@@ -0,0 +1,127 @@
/*
* 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.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 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();
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;
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()];
// 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);
}
// Generate CAPTCHA answer
final char[] answer = new char[5];
for (int j = 0; j < answer.length; j++) {
answer[j] = dictionary[ThreadLocalRandom.current().nextInt(dictionary.length)];
}
final BufferedImage image = generator.createImage(answer, filters);
// Convert and cache converted Minecraft map bytes
final int[] buffer = MapColorPalette.getBufferFromImage(image);
cached[preparedAmount] = new MapCaptchaInfo(new String(answer), buffer);
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
});
}
public boolean isCaptchaAvailable() {
return cached != null && cached[0] != null;
}
public MapCaptchaInfo getRandomCaptcha() {
// Give the player a random CAPTCHA out of the ones that we've already prepared
return cached[RANDOM.nextInt(preparedAmount)];
}
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.map;
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -72,6 +72,8 @@ public enum ItemType {
return 941;
case MINECRAFT_1_20_3:
return 979;
case MINECRAFT_1_20_5:
return 982;
}
});
@@ -15,11 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.map;
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.MapDataPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@@ -27,22 +28,20 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
@Getter
public final class MapCaptchaInfo {
private final String answer;
private final byte[] buffer;
private final MapDataPacket[] legacy;
private final MapDataPacket modern;
private final FallbackPacket[] legacy;
private final FallbackPacket modern;
public MapCaptchaInfo(final @NotNull String answer, final byte @NotNull [] buffer) {
public MapCaptchaInfo(final @NotNull String answer, final int @NotNull [] buffer) {
this.answer = answer;
this.buffer = buffer;
// Prepare 1.7 map data using a grid
final byte[][] grid = new byte[128][128];
final int[][] grid = new int[128][128];
for (int i = 0; i < buffer.length; i++) {
final byte buf = buffer[i];
final int buf = buffer[i];
grid[i & Byte.MAX_VALUE][i >> 7] = buf;
}
this.legacy = new MapDataPacket[grid.length];
this.legacy = new FallbackPacket[grid.length];
for (int i = 0; i < grid.length; i++) {
this.legacy[i] = new MapDataPacket(grid[i], i, 0);
}
@@ -52,14 +51,14 @@ public final class MapCaptchaInfo {
}
public void delayedWrite(final @NotNull FallbackUser user) {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
user.delayedWrite(modern);
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
// 1.7.2-1.7.10 needs separate packets for each axis
for (final FallbackPacket legacyPacket : legacy) {
user.delayedWrite(legacyPacket);
}
return;
}
// 1.7.2-1.7.10
for (final MapDataPacket legacyPacket : legacy) {
user.delayedWrite(legacyPacket);
}
// Send modern packet
user.delayedWrite(modern);
}
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.map;
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
@@ -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 = {
0,
0,
0,
0,
-16777216,
-16777216,
-16777216,
-16777216,
-10912473,
-9594576,
-8408520,
@@ -38,10 +38,10 @@ public class MapColorPalette {
-2766452,
-530013,
-8225962,
-7697782,
-5658199,
-3815995,
-9934744,
-7566196,
-5526613,
-3684409,
-9868951,
-4980736,
-2359296,
-65536,
@@ -66,22 +66,22 @@ public class MapColorPalette {
-7499618,
-5986120,
-11118495,
-8304095,
-6464728,
-4756945,
-10471400,
-9810890,
-8233406,
-6853299,
-11585240,
-11579569,
-10461088,
-9408400,
-12895429,
-10136525,
-8689858,
-7375032,
-11780314,
-13816396,
-13158436,
-12566273,
-14605945,
-10202062,
-8690114,
-7375032,
-11845850,
-4935252,
-2303533,
-779,
@@ -153,19 +153,34 @@ public class MapColorPalette {
-12543338,
-11551561,
-10691627,
-13601936,
-13346124,
-12620068,
-11894529,
-14204025,
-16738008,
-16729294,
-16721606,
-16748002,
-10798046,
-9483734,
-8301007,
-12309223,
-11599616,
-10485504,
-9436672,
-12910336,
};
public byte[] getBufferFromImage(final @NotNull BufferedImage bufferedImage) {
final int width = bufferedImage.getWidth();
final int height = bufferedImage.getHeight();
public int[] getBufferFromImage(final @NotNull BufferedImage bufferedImage) {
final int[] buffer = new int[128 * 128];
final int[] pixels = new int[128 * 128];
final byte[] buffer = new byte[128 * 128];
bufferedImage.getRGB(0, 0,
bufferedImage.getWidth(), bufferedImage.getHeight(),
buffer, 0, bufferedImage.getWidth());
bufferedImage.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
buffer[i] = mapColorToMinecraft(pixels[i]);
for (int i = 0; i < buffer.length; i++) {
buffer[i] = mapColorToMinecraft(buffer[i]);
}
return buffer;
}
@@ -173,11 +188,12 @@ public class MapColorPalette {
private byte mapColorToMinecraft(final int argb) {
int minDistance = Integer.MAX_VALUE;
byte closestIndex = 0;
for (byte i = 0; i < COLOR_MAP.length; i++) {
for (int i = 0; i < COLOR_MAP.length; i++) {
final byte index = (byte) (i < 128 ? i : -129 + (i - 127));
final int distance = calculateColorDistance(argb, COLOR_MAP[i]);
if (distance < minDistance) {
minDistance = distance;
closestIndex = i;
closestIndex = index;
}
}
return closestIndex;
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.vehicle;
package xyz.jonesdev.sonar.common.fallback.protocol.entity;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -77,6 +77,8 @@ public enum EntityType {
case MINECRAFT_1_20_2:
case MINECRAFT_1_20_3:
return 9;
case MINECRAFT_1_20_5:
return 10;
}
});
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.map;
import xyz.jonesdev.capja.CaptchaGenerator;
import xyz.jonesdev.capja.CaptchaHolder;
import xyz.jonesdev.capja.config.CaptchaConfiguration;
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.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class CaptchaPreparer {
private static final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
private static 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;
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()];
// Prepare everything asynchronously
PREPARATION_SERVICE.execute(() -> {
final List<String> fonts = config.getFonts();
if (fonts.isEmpty()) {
throw new IllegalArgumentException("No fonts specified!");
}
// Create the configuration for the image generation
final String[] fontNames = fonts.toArray(new String[0]);
final CaptchaConfiguration configuration = new CaptchaConfiguration(
128, 128, config.getDictionary().toCharArray(), 5,
config.isFlare(), config.isScratches(), config.isRipple(), config.isSmear(), config.isPinch(),
config.getSaturation(), config.getDistortion(), new int[]{Font.PLAIN, Font.BOLD}, fontNames);
for (int i = 0; i < config.getPrecomputeAmount(); i++) {
final CaptchaGenerator captchaGenerator = new CaptchaGenerator(configuration);
final CaptchaHolder captchaHolder = captchaGenerator.generate();
final byte[] buffer = MapColorPalette.getBufferFromImage(captchaHolder.getImage());
cached[i] = new MapCaptchaInfo(captchaHolder.getAnswer(), buffer);
preparedAmount++;
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
});
}
public boolean isCaptchaAvailable() {
return cached != null && cached[0] != null;
}
public MapCaptchaInfo getRandomCaptcha() {
// Give the player a random CAPTCHA out of the ones that we've already prepared
return cached[RANDOM.nextInt(preparedAmount)];
}
}
@@ -18,11 +18,9 @@
package xyz.jonesdev.sonar.common.fallback.protocol.packets.config;
import io.netty.buffer.ByteBuf;
import lombok.ToString;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@ToString
public final class FinishConfigurationPacket implements FallbackPacket {
@Override
@@ -35,6 +33,7 @@ public final class FinishConfigurationPacket implements FallbackPacket {
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
// This packet is always empty; see https://wiki.vg/Protocol#Finish_Configuration
return 0;
}
}
@@ -18,26 +18,74 @@
package xyz.jonesdev.sonar.common.fallback.protocol.packets.config;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import lombok.ToString;
import lombok.*;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketSnapshot;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
import java.util.ArrayList;
import java.util.List;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class RegistryDataPacket implements FallbackPacket {
private CompoundBinaryTag tag;
private String type;
private List<RegistryDataPacket.Bundle> bundles;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
writeNamelessCompoundTag(byteBuf, DimensionRegistry.CODEC_1_20);
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_5) < 0) {
writeNamelessCompoundTag(byteBuf, tag);
} else if (type != null) {
writeString(byteBuf, type);
writeVarInt(byteBuf, bundles.size());
for (final RegistryDataPacket.Bundle bundle : bundles) {
writeString(byteBuf, bundle.getName());
// Write the bundle tag
final CompoundBinaryTag tag = bundle.getTag();
if (tag != null) {
byteBuf.writeBoolean(true);
writeNamelessCompoundTag(byteBuf, tag);
} else {
byteBuf.writeBoolean(false);
}
}
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
public static FallbackPacket @NotNull [] of(final @NotNull CompoundBinaryTag rootTag) {
final FallbackPacket[] packets = new FallbackPacket[rootTag.size()];
int index = 0;
for (final String type : rootTag.keySet()) {
final ArrayList<RegistryDataPacket.Bundle> bundles = new ArrayList<>();
for (final BinaryTag binaryTag : rootTag.getCompound(type).getList("value")) {
final CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
bundles.add(new Bundle(tag.getString("name"), tag.getCompound("element")));
}
packets[index++] = new FallbackPacketSnapshot(new RegistryDataPacket(rootTag, type, bundles));
}
return packets;
}
@Data
@Value
public static class Bundle {
@NotNull String name;
// In the protocol, it is able to be null
@Nullable CompoundBinaryTag tag;
}
}
@@ -25,6 +25,7 @@ public final class LoginAcknowledgedPacket implements FallbackPacket {
@Override
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
@@ -33,7 +34,7 @@ public final class LoginAcknowledgedPacket implements FallbackPacket {
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java#L41
// This packet is always empty; see https://wiki.vg/Protocol#Login_Acknowledged
return 0;
}
}
@@ -24,25 +24,24 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.netty.FastUUID;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.util.FastUuidSansHyphens;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class LoginSuccessPacket implements FallbackPacket {
private String username;
private UUID uuid;
private String username;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeUUID(byteBuf, uuid);
} else if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0) {
@@ -50,7 +49,7 @@ public final class LoginSuccessPacket implements FallbackPacket {
} else if (protocolVersion.compareTo(MINECRAFT_1_7_6) >= 0) {
writeString(byteBuf, uuid.toString());
} else {
writeString(byteBuf, FastUUID.toString(uuid));
writeString(byteBuf, FastUuidSansHyphens.toString(uuid));
}
writeString(byteBuf, username);
@@ -58,6 +57,9 @@ public final class LoginSuccessPacket implements FallbackPacket {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
writeVarInt(byteBuf, 0); // properties
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
byteBuf.writeBoolean(false); // should authenticate
}
}
@Override
@@ -29,8 +29,7 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@@ -67,7 +66,7 @@ public final class ChunkDataPacket implements FallbackPacket {
}
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeInt(x);
byteBuf.writeInt(z);
@@ -30,12 +30,12 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class ClientAbilitiesPacket implements FallbackPacket {
public final class ClientAbilitiesPacket implements FallbackPacket {
private int encodedFlags;
private float flySpeed, walkSpeed;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeByte(encodedFlags);
byteBuf.writeFloat(flySpeed);
byteBuf.writeFloat(walkSpeed);
@@ -27,8 +27,8 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.readString;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
@Getter
@ToString
@@ -39,7 +39,7 @@ public final class ClientSettingsPacket implements FallbackPacket {
private byte viewDistance;
private int chatVisibility;
private boolean chatColors;
private byte difficulty; // 1.7 Protocol
private byte difficulty; // 1.7
private short skinParts;
private int mainHand;
private boolean chatFilteringEnabled; // Added in 1.17
@@ -51,7 +51,7 @@ public final class ClientSettingsPacket implements FallbackPacket {
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
locale = readString(byteBuf, 16);
viewDistance = byteBuf.readByte();
chatVisibility = readVarInt(byteBuf);
@@ -37,7 +37,7 @@ public final class DefaultSpawnPositionPacket implements FallbackPacket {
private int x, y, z;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
byteBuf.writeInt(x);
byteBuf.writeInt(y);
@@ -26,7 +26,7 @@ import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.utility.component.ComponentHolder;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
@Getter
@ToString
@@ -37,7 +37,7 @@ public final class DisconnectPacket implements FallbackPacket {
private boolean login;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
componentHolder.write(byteBuf, login ? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion);
}
@@ -46,8 +46,7 @@ public final class DisconnectPacket implements FallbackPacket {
throw new UnsupportedOperationException();
}
public static @NotNull DisconnectPacket create(final @NotNull Component component,
final boolean login) {
return new DisconnectPacket(new ComponentHolder(component), login);
public static @NotNull DisconnectPacket create(final @NotNull Component component, final boolean duringLogin) {
return new DisconnectPacket(new ComponentHolder(component), duringLogin);
}
}
@@ -33,7 +33,7 @@ public final class GameEventPacket implements FallbackPacket {
private float value;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeByte(type);
byteBuf.writeFloat(value);
}
@@ -27,8 +27,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry.*;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
// Mostly taken from
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/packets/play/PacketJoinGame.java
@@ -48,7 +47,7 @@ public final class JoinGamePacket implements FallbackPacket {
private String levelName;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeInt(entityId);
if (protocolVersion.inBetween(MINECRAFT_1_7_2, MINECRAFT_1_7_6)) {
@@ -210,7 +209,7 @@ public final class JoinGamePacket implements FallbackPacket {
return;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
if (protocolVersion.inBetween(MINECRAFT_1_20_2, MINECRAFT_1_20_3)) {
byteBuf.writeBoolean(isHardcore);
writeStringArray(byteBuf, levelNames);
writeVarInt(byteBuf, 0); // max players
@@ -228,6 +227,29 @@ public final class JoinGamePacket implements FallbackPacket {
byteBuf.writeBoolean(false); // flat
byteBuf.writeBoolean(false); // no last death location
writeVarInt(byteBuf, 0);
return;
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
byteBuf.writeBoolean(isHardcore);
writeStringArray(byteBuf, levelNames);
writeVarInt(byteBuf, 0); // max players
writeVarInt(byteBuf, viewDistance);
writeVarInt(byteBuf, viewDistance); // simulation distance
byteBuf.writeBoolean(reducedDebugInfo);
byteBuf.writeBoolean(showRespawnScreen);
byteBuf.writeBoolean(false); // limited crafting
// It depends on the dimension data we send in RegistryData. If we only send one dimension. It should be 0.
writeVarInt(byteBuf, 0);
writeString(byteBuf, levelName);
byteBuf.writeLong(partialHashedSeed);
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
byteBuf.writeBoolean(false); // debug type
byteBuf.writeBoolean(false); // flat
byteBuf.writeBoolean(false); // no last death location
writeVarInt(byteBuf, 0); // pearl cooldown
byteBuf.writeBoolean(false); // secure profile
}
}
@@ -28,8 +28,8 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_12_2;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@@ -39,7 +39,7 @@ public final class KeepAlivePacket implements FallbackPacket {
private long id;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
byteBuf.writeLong(id);
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
@@ -50,7 +50,7 @@ public final class KeepAlivePacket implements FallbackPacket {
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
id = byteBuf.readLong();
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
@@ -21,19 +21,17 @@ import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class MapDataPacket implements FallbackPacket {
private byte[] buffer;
private int[] buffer;
private int x, y;
@Override
@@ -45,8 +43,7 @@ public final class MapDataPacket implements FallbackPacket {
byteBuf.writeByte(0); // scaling
byteBuf.writeByte(x);
byteBuf.writeByte(y);
byteBuf.writeBytes(buffer);
writeArray(byteBuf, buffer);
} else {
byteBuf.writeByte(0); // scaling
@@ -71,7 +68,13 @@ public final class MapDataPacket implements FallbackPacket {
byteBuf.writeByte(y);
writeVarInt(byteBuf, buffer.length);
byteBuf.writeBytes(buffer);
writeArray(byteBuf, buffer);
}
}
private static void writeArray(final ByteBuf byteBuf, final int @NotNull [] array) {
for (final int i : array) {
byteBuf.writeByte(i);
}
}
@@ -0,0 +1,46 @@
/*
* 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.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class PaddleBoatPacket implements FallbackPacket {
private boolean leftPaddle, rightPaddle;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
leftPaddle = byteBuf.readBoolean();
rightPaddle = byteBuf.readBoolean();
}
}
@@ -30,7 +30,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PlayerInputPacket implements FallbackPacket {
public final class PlayerInputPacket implements FallbackPacket {
private float sideways, forward;
private boolean jump, unmount;
@@ -40,7 +40,7 @@ public class PlayerInputPacket implements FallbackPacket {
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
sideways = byteBuf.readFloat();
forward = byteBuf.readFloat();
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
@@ -30,7 +30,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class PlayerTickPacket implements FallbackPacket {
public final class PlayerPositionIdlePacket implements FallbackPacket {
private boolean onGround;
@Override
@@ -39,7 +39,7 @@ public final class PlayerTickPacket implements FallbackPacket {
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
onGround = byteBuf.readBoolean();
}
}
@@ -27,7 +27,7 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@@ -64,10 +64,10 @@ public final class PlayerPositionLookPacket implements FallbackPacket {
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
x = byteBuf.readDouble();
y = byteBuf.readDouble();
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
y = byteBuf.readDouble(); // Account for weird y positions on 1.7
}
z = byteBuf.readDouble();
@@ -78,7 +78,7 @@ public final class PlayerPositionLookPacket implements FallbackPacket {
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 41 : 33;
return protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 41 : 33;
}
@Override
@@ -26,6 +26,8 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
@Getter
@ToString
@NoArgsConstructor
@@ -40,10 +42,10 @@ public final class PlayerPositionPacket implements FallbackPacket {
}
@Override
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
x = byteBuf.readDouble();
y = byteBuf.readDouble();
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
y = byteBuf.readDouble(); // Account for weird y positions on 1.7
}
z = byteBuf.readDouble();
@@ -52,7 +54,7 @@ public final class PlayerPositionPacket implements FallbackPacket {
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 33 : 25;
return protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 33 : 25;
}
@Override
@@ -24,9 +24,9 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_13;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readRetainedByteBufSlice17;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
@Getter
@ToString
@@ -40,12 +40,8 @@ public final class PluginMessagePacket implements FallbackPacket {
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
channel = readString(byteBuf, 512);
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
channel = transformLegacyToModernChannel(channel);
}
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
channel = readString(byteBuf, 64);
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
slicedBuffer = byteBuf.readRetainedSlice(byteBuf.readableBytes());
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@@ -36,7 +36,7 @@ public final class RemoveEntitiesPacket implements FallbackPacket {
private int entityId; // We only need one entityId
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// No idea why Mojang decided that this is a good idea, but whatever
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) != 0) {
writeVarInt(byteBuf, 1); // size
@@ -0,0 +1,56 @@
/*
* 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.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class SetExperiencePacket implements FallbackPacket {
private float experienceBar;
private int level;
private int totalExperience;
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeFloat(experienceBar);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
byteBuf.writeShort(level);
byteBuf.writeShort(totalExperience);
} else {
writeVarInt(byteBuf, level);
writeVarInt(byteBuf, totalExperience);
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
}
@@ -26,18 +26,17 @@ import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class SetPassengersPacket implements FallbackPacket {
private int entityId;
private int passengerId;
private int entityId, passengerId;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// You can find this in the EntityAttach packet,
// which was later replaced by SetPassengers in 1.9+
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) < 0) {
@@ -29,20 +29,14 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeCompoundTag;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class SetSlotPacket implements FallbackPacket {
private int windowId;
private int slot;
private int count;
private int data;
private int itemId;
private int slot, count, itemId;
private CompoundBinaryTag compoundBinaryTag;
public static final CompoundBinaryTag MAP_NBT = CompoundBinaryTag.builder()
@@ -50,8 +44,8 @@ public final class SetSlotPacket implements FallbackPacket {
.build();
@Override
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
byteBuf.writeByte(windowId);
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
byteBuf.writeByte(0); // windowId
if (protocolVersion.compareTo(MINECRAFT_1_17_1) >= 0) {
writeVarInt(byteBuf, 0);
@@ -59,31 +53,47 @@ public final class SetSlotPacket implements FallbackPacket {
byteBuf.writeShort(slot);
if (protocolVersion.compareTo(MINECRAFT_1_13_2) >= 0) {
if (protocolVersion.inBetween(MINECRAFT_1_13_2, MINECRAFT_1_20_3)) {
byteBuf.writeBoolean(true);
}
if (protocolVersion.compareTo(MINECRAFT_1_20_5) >= 0) {
writeVarInt(byteBuf, count);
}
if (protocolVersion.compareTo(MINECRAFT_1_13_2) < 0) {
byteBuf.writeShort(itemId);
} else {
writeVarInt(byteBuf, itemId);
}
byteBuf.writeByte(count);
if (protocolVersion.compareTo(MINECRAFT_1_20_5) < 0) {
byteBuf.writeByte(count);
}
if (protocolVersion.compareTo(MINECRAFT_1_13) < 0) {
byteBuf.writeShort(data);
byteBuf.writeShort(0); // data
}
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
byteBuf.writeShort(-1);
} else {
byteBuf.writeByte(0);
}
} else {
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
writeNamelessCompoundTag(byteBuf, compoundBinaryTag);
} else {
if (protocolVersion.compareTo(MINECRAFT_1_20_2) < 0) {
writeCompoundTag(byteBuf, compoundBinaryTag);
} else if (protocolVersion.compareTo(MINECRAFT_1_20_5) < 0) {
writeNamelessCompoundTag(byteBuf, compoundBinaryTag);
} else { // 1.20.5
// TODO: find a way to improve this
// component
writeVarInt(byteBuf, 1); // component count to add
writeVarInt(byteBuf, 0); // component count to remove
// single VarInt component
writeVarInt(byteBuf, 26); // map component
writeVarInt(byteBuf, 0); // map id
}
}
}
@@ -27,9 +27,8 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.netty.FastUUID.EMPTY_UUID;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeUUID;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeUUID;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@@ -40,7 +39,7 @@ public final class SpawnEntityPacket implements FallbackPacket {
private double x, y, z;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
writeVarInt(byteBuf, entityId);
final boolean v1_9orHigher = protocolVersion.compareTo(MINECRAFT_1_8) > 0;
@@ -25,7 +25,7 @@ import lombok.ToString;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
@Getter
@ToString
@@ -40,7 +40,7 @@ public final class TeleportConfirmPacket implements FallbackPacket {
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
teleportId = readVarInt(byteBuf);
}
}
@@ -33,17 +33,17 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
@NoArgsConstructor
@AllArgsConstructor
public final class TransactionPacket implements FallbackPacket {
private int windowId, id;
private int windowId, transactionId;
private boolean accepted;
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
windowId = byteBuf.readByte();
id = byteBuf.readShort();
transactionId = byteBuf.readShort();
accepted = byteBuf.readBoolean();
} else {
id = byteBuf.readInt();
transactionId = byteBuf.readInt();
// Always set accepted to true since 1.17 or higher don't use
// transactions for inventory confirmation anymore.
accepted = true;
@@ -51,15 +51,15 @@ public final class TransactionPacket implements FallbackPacket {
}
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
byteBuf.writeByte(windowId);
byteBuf.writeShort((short) id);
byteBuf.writeShort((short) transactionId);
// The "accepted" field is actually really unnecessary since
// it's never even used in the client.
byteBuf.writeBoolean(accepted);
} else {
byteBuf.writeInt(id);
byteBuf.writeInt(transactionId);
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 jones
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class TransferPacket implements FallbackPacket {
private String destinationHost;
private int destinationPort;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
writeString(byteBuf, destinationHost);
writeVarInt(byteBuf, destinationPort);
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
}
@@ -26,15 +26,10 @@ import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.utility.component.ComponentHolder;
import java.time.Instant;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.fallback.netty.FastUUID.EMPTY_UUID;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Data
@NoArgsConstructor
@@ -49,16 +44,9 @@ public final class UniversalChatPacket implements FallbackPacket {
private ComponentHolder componentHolder;
private String message;
private byte type;
private boolean signedPreview;
private boolean unsigned = false;
private Instant timestamp;
private Instant expiry;
private long salt;
private boolean signed;
private byte[] signature;
public UniversalChatPacket(final @NotNull Component component,
final byte type) {
public UniversalChatPacket(final @NotNull Component component, final byte type) {
this(new ComponentHolder(component), type);
}
@@ -68,7 +56,7 @@ public final class UniversalChatPacket implements FallbackPacket {
}
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
// Serialized message
componentHolder.write(byteBuf, protocolVersion);
@@ -89,31 +77,31 @@ public final class UniversalChatPacket implements FallbackPacket {
}
@Override
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
message = readString(byteBuf, 256);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) <= 0) {
final long expiresAt = byteBuf.readLong();
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_19_1) <= 0) {
byteBuf.readLong(); // expiresAt
final long saltLong = byteBuf.readLong();
final byte[] signatureBytes = readByteArray(byteBuf);
boolean unsigned = false;
if (saltLong != 0L && signatureBytes.length > 0) {
signature = signatureBytes;
expiry = Instant.ofEpochMilli(expiresAt);
} else if ((protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0
} else if ((protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0
|| saltLong == 0L) && signatureBytes.length == 0) {
unsigned = true;
} else {
throw new CorruptedFrameException("Invalid signature");
}
signedPreview = byteBuf.readBoolean();
final boolean signedPreview = byteBuf.readBoolean();
if (signedPreview && unsigned) {
throw new CorruptedFrameException("Signature missing");
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
final int size = readVarInt(byteBuf);
if (size < 0 || size > 5) {
throw new CorruptedFrameException("Invalid previous messages");
@@ -130,11 +118,12 @@ public final class UniversalChatPacket implements FallbackPacket {
}
}
} else {
timestamp = Instant.ofEpochMilli(byteBuf.readLong());
salt = byteBuf.readLong();
signed = byteBuf.readBoolean();
byteBuf.readLong(); // timestamp
byteBuf.readLong(); // salt
final boolean signed = byteBuf.readBoolean();
if (signed) {
byte[] sign = new byte[256];
final byte[] sign = new byte[256];
byteBuf.readBytes(sign);
signature = sign;
} else {
@@ -142,7 +131,7 @@ public final class UniversalChatPacket implements FallbackPacket {
}
readVarInt(byteBuf);
byte[] bytes = new byte[DIV_FLOOR];
final byte[] bytes = new byte[DIV_FLOOR];
byteBuf.readBytes(bytes);
}
}
@@ -25,19 +25,19 @@ import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.block.ChangedBlock;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarLong;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarLong;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"changedBlocks"})
@ToString(exclude = {"blockUpdates"})
public final class UpdateSectionBlocksPacket implements FallbackPacket {
private int sectionX, sectionZ;
private ChangedBlock[] changedBlocks;
private BlockUpdate[] blockUpdates;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
@@ -46,13 +46,13 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
byteBuf.writeInt(sectionZ);
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
byteBuf.writeShort(changedBlocks.length);
byteBuf.writeInt(4 * changedBlocks.length);
byteBuf.writeShort(blockUpdates.length);
byteBuf.writeInt(4 * blockUpdates.length);
} else {
writeVarInt(byteBuf, changedBlocks.length);
writeVarInt(byteBuf, blockUpdates.length);
}
for (final ChangedBlock block : changedBlocks) {
for (final BlockUpdate block : blockUpdates) {
byteBuf.writeShort(block.getLegacyBlockState());
final int blockId = block.getType().getId(protocolVersion);
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
@@ -68,7 +68,7 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
}
} else {
// We only need one Y position
final int sectionY = changedBlocks[0].getPosition().getY() >> 4;
final int sectionY = blockUpdates[0].getPosition().getY() >> 4;
// https://wiki.vg/Protocol#Update_Section_Blocks
byteBuf.writeLong(((sectionX & 0x3FFFFFL) << 42) | (sectionY & 0xFFFFF) | ((sectionZ & 0x3FFFFFL) << 20));
@@ -78,9 +78,9 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
byteBuf.writeBoolean(true); // suppress light updates
}
writeVarInt(byteBuf, changedBlocks.length);
writeVarInt(byteBuf, blockUpdates.length);
for (final ChangedBlock block : changedBlocks) {
for (final BlockUpdate block : blockUpdates) {
// https://wiki.vg/Protocol#Update_Section_Blocks
final int shiftedBlockId = block.getType().getId(protocolVersion) << 12;
final long positionIdValue = shiftedBlockId | block.getBlockState();
@@ -15,16 +15,28 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.block;
package xyz.jonesdev.sonar.common.service;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
public final class SonarServiceThread extends Thread {
private final long delay;
private final Runnable runnable;
@Getter
@ToString
@RequiredArgsConstructor
public final class BlockPosition {
private final int x, y, z;
private final int chunkX, chunkZ;
public SonarServiceThread(final String name, final long delay, final Runnable runnable) {
setName(name);
this.delay = delay;
this.runnable = runnable;
}
@Override
public void run() {
try {
while (!isInterrupted()) {
runnable.run();
// Sleep thread by specified delay
Thread.sleep(delay);
}
} catch (InterruptedException interruptedException) {
//
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.common.service;
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
@UtilityClass
public final class SonarServiceThreadManager {
private static final SonarServiceThread VERBOSE = new SonarServiceThread("sonar-verbose-thread", 250L,
() -> {
// Make sure to clean up the cached statistics
// since we don't want to display wrong values.
Sonar.get().getStatistics().cleanUpCache();
// Update the attack tracker
Sonar.get().getAttackTracker().checkIfUnderAttack();
// Update the action bar verbose
Sonar.get().getVerboseHandler().update();
});
private static final SonarServiceThread FALLBACK_QUEUE = new SonarServiceThread("sonar-fallback-queue-thread", 1000L,
() -> Sonar.get().getFallback().getQueue().poll());
private static final SonarServiceThread STATISTICS = new SonarServiceThread("sonar-statistics-thread", 1000L,
() -> {
// Clean up bandwidth statistics
CachedBandwidthStatistics.reset();
// Clean up the cache of rate-limited IPs
Sonar.get().getFallback().getRatelimiter().cleanUpCache();
});
public void start() {
VERBOSE.start();
FALLBACK_QUEUE.start();
STATISTICS.start();
}
public void stop() {
VERBOSE.interrupt();
FALLBACK_QUEUE.interrupt();
STATISTICS.interrupt();
}
}
@@ -47,10 +47,9 @@ public enum CachedBandwidthStatistics {
* (per-second) value and resets the current value.
* This method is called every second.
*/
public static synchronized void reset() {
for (final CachedBandwidthStatistics value : values()) {
value.cacheAndReset();
}
public static void reset() {
INCOMING.cacheAndReset();
OUTGOING.cacheAndReset();
}
private void cacheAndReset() {
@@ -63,7 +63,7 @@ public final class GlobalSonarStatistics implements SonarStatistics {
* Called every time the action bar updates to ensure fresh values
*/
@Override
public synchronized void cleanUpCache() {
public void cleanUpCache() {
LOGINS_PER_SECOND.cleanUp();
CONNECTIONS_PER_SECOND.cleanUp();
// Also clean up the blacklist
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.component;
package xyz.jonesdev.sonar.common.util;
import com.google.gson.*;
import com.google.gson.internal.LazilyParsedNumber;
@@ -31,8 +31,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeNamelessCompoundTag;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeString;
// Mostly taken from
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
@@ -15,50 +15,81 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.netty;
package xyz.jonesdev.sonar.common.util;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Jon Chambers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// Taken from
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
public final class FastUUID {
public static final UUID EMPTY_UUID = new UUID(0L, 0L);
/**
* This is a modified FastUUID implementation. The primary difference is that it does not dash its
* UUIDs. As the native Java 9+ UUID.toString() implementation dashes its UUIDs, we use the FastUUID
* methods, which ought to be faster than a String.replace().
*/
@UtilityClass
public class FastUuidSansHyphens {
private static final int MOJANG_BROKEN_UUID_LENGTH = 32;
private static final char[] HEX_DIGITS =
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* Returns a string representation of the given UUID. The returned string is formatted as a
* Mojang-style UUID.
*
* @param uuid the UUID to represent as a string
*
* @return a string representation of the given UUID
*/
public static String toString(final @NotNull UUID uuid) {
public static @NotNull String toString(final @NotNull UUID uuid) {
final long mostSignificantBits = uuid.getMostSignificantBits();
final long leastSignificantBits = uuid.getLeastSignificantBits();
final char[] uuidChars = new char[MOJANG_BROKEN_UUID_LENGTH];
uuidChars[0] = HEX_DIGITS[(int) ((mostSignificantBits & 0xf000000000000000L) >>> 60)];
uuidChars[1] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0f00000000000000L) >>> 56)];
uuidChars[2] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00f0000000000000L) >>> 52)];
uuidChars[3] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000f000000000000L) >>> 48)];
uuidChars[4] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000f00000000000L) >>> 44)];
uuidChars[5] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000f0000000000L) >>> 40)];
uuidChars[6] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000f000000000L) >>> 36)];
uuidChars[7] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000f00000000L) >>> 32)];
uuidChars[8] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000f0000000L) >>> 28)];
uuidChars[9] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000f000000L) >>> 24)];
uuidChars[0] = HEX_DIGITS[(int) ((mostSignificantBits & 0xf000000000000000L) >>> 60)];
uuidChars[1] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0f00000000000000L) >>> 56)];
uuidChars[2] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00f0000000000000L) >>> 52)];
uuidChars[3] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000f000000000000L) >>> 48)];
uuidChars[4] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000f00000000000L) >>> 44)];
uuidChars[5] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000f0000000000L) >>> 40)];
uuidChars[6] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000f000000000L) >>> 36)];
uuidChars[7] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000f00000000L) >>> 32)];
uuidChars[8] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000f0000000L) >>> 28)];
uuidChars[9] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000f000000L) >>> 24)];
uuidChars[10] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000f00000L) >>> 20)];
uuidChars[11] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000f0000L) >>> 16)];
uuidChars[12] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000000f000L) >>> 12)];
uuidChars[13] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000000f00L) >>> 8)];
uuidChars[14] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000000f0L) >>> 4)];
uuidChars[15] = HEX_DIGITS[(int) (mostSignificantBits & 0x000000000000000fL)];
uuidChars[15] = HEX_DIGITS[(int) (mostSignificantBits & 0x000000000000000fL)];
uuidChars[16] = HEX_DIGITS[(int) ((leastSignificantBits & 0xf000000000000000L) >>> 60)];
uuidChars[17] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0f00000000000000L) >>> 56)];
uuidChars[18] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00f0000000000000L) >>> 52)];
@@ -74,7 +105,7 @@ public final class FastUUID {
uuidChars[28] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000000f000L) >>> 12)];
uuidChars[29] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000000f00L) >>> 8)];
uuidChars[30] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000000f0L) >>> 4)];
uuidChars[31] = HEX_DIGITS[(int) (leastSignificantBits & 0x000000000000000fL)];
uuidChars[31] = HEX_DIGITS[(int) (leastSignificantBits & 0x000000000000000fL)];
return new String(uuidChars);
}
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.geyser;
package xyz.jonesdev.sonar.common.util;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
@@ -28,7 +28,7 @@ import java.net.InetSocketAddress;
* Simple utility to determine if someone joins using GeyserMC
*/
@UtilityClass
public class GeyserUtil {
public class GeyserDetection {
// https://github.com/GeyserMC/Floodgate/blob/master/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java#L206
private final AttributeKey<Object> PLAYER_ATTRIBUTE = AttributeKey.valueOf("floodgate-player");
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.protocol;
package xyz.jonesdev.sonar.common.util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
@@ -31,26 +31,138 @@ import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.regex.Pattern;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
// Taken from
// Mostly taken from
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
@UtilityClass
public class ProtocolUtil {
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
private static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
private static final String BRAND_CHANNEL = "minecraft:brand";
private static final String REGISTER_CHANNEL_LEGACY = "REGISTER";
private static final String REGISTER_CHANNEL = "minecraft:register";
private static final String UNREGISTER_CHANNEL_LEGACY = "UNREGISTER";
private static final String UNREGISTER_CHANNEL = "minecraft:unregister";
private static final Pattern INVALID_IDENTIFIER_REGEX = Pattern.compile("[^a-z0-9\\-_]*");
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
public static final String BRAND_CHANNEL = "minecraft:brand";
public static @NotNull UUID readUUID(final @NotNull ByteBuf byteBuf) {
return new UUID(byteBuf.readLong(), byteBuf.readLong());
public static int readVarInt(final ByteBuf byteBuf) {
int read = readVarIntSafely(byteBuf);
if (read == Integer.MIN_VALUE) {
throw new CorruptedFrameException("Corrupt VarInt");
}
return read;
}
public static int readVarIntSafely(final @NotNull ByteBuf byteBuf) {
int i = 0;
int maxRead = Math.min(5, byteBuf.readableBytes());
for (int j = 0; j < maxRead; j++) {
int k = byteBuf.readByte();
i |= (k & 0x7F) << j * 7;
if ((k & 0x80) != 128) {
return i;
}
}
return Integer.MIN_VALUE;
}
public static void writeVarInt(final ByteBuf byteBuf, final int value) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
// that the proxy will write, to improve inlining.
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
byteBuf.writeShort(w);
} else {
writeVarIntFull(byteBuf, value);
}
}
private void writeVarIntFull(final ByteBuf byteBuf, final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
byteBuf.writeShort(w);
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
byteBuf.writeMedium(w);
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
byteBuf.writeInt(w);
} else {
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
byteBuf.writeInt(w);
byteBuf.writeByte(value >>> 28);
}
}
public static void writeVarLong(final ByteBuf byteBuf, final long value) {
// Peel the one and two byte count cases explicitly as they are the most common VarLong sizes
// that the proxy will write, to improve inlining.
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
byteBuf.writeByte((byte) value);
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 8 | value >>> 7);
byteBuf.writeShort(w);
} else {
writeVarLongFull(byteBuf, value);
}
}
private void writeVarLongFull(final ByteBuf byteBuf, final long value) {
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
byteBuf.writeByte((byte) value);
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 8 | value >>> 7);
byteBuf.writeShort(w);
} else if ((value & 0xFFFFFFFFFFE00000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 16 | (value >>> 7 & 0x7FL | 0x80L) << 8 | value >>> 14);
byteBuf.writeMedium(w);
} else if ((value & 0xFFFFFFFFF0000000L) == 0L) {
int w =
(int) ((value & 0x7FL | 0x80L) << 24 | (value >>> 7 & 0x7FL | 0x80L) << 16 | (value >>> 14 & 0x7FL | 0x80L) << 8 | value >>> 21);
byteBuf.writeInt(w);
} else {
long l =
(value & 0x7FL | 0x80L) << 24 | (value >>> 7 & 0x7FL | 0x80L) << 16 | (value >>> 14 & 0x7FL | 0x80L) << 8 | (value >>> 21 & 0x7FL | 0x80L);
if ((value & 0xFFFFFFF800000000L) == 0L) {
int w =
(int) l;
byteBuf.writeInt(w);
byteBuf.writeByte((int) (value >>> 28));
} else if ((value & 0xFFFFFC0000000000L) == 0L) {
int w =
(int) l;
int w2 = (int) ((value >>> 28 & 0x7FL | 0x80L) << 8 | value >>> 35);
byteBuf.writeInt(w);
byteBuf.writeShort(w2);
} else if ((value & 0xFFFE000000000000L) == 0L) {
int w =
(int) l;
int w2 = (int) ((value >>> 28 & 0x7FL | 0x80L) << 16 | (value >>> 35 & 0x7FL | 0x80L) << 8 | value >>> 42);
byteBuf.writeInt(w);
byteBuf.writeMedium(w2);
} else {
long w =
(value & 0x7FL | 0x80L) << 56 | (value >>> 7 & 0x7FL | 0x80L) << 48 | (value >>> 14 & 0x7FL | 0x80L) << 40 | (value >>> 21 & 0x7FL | 0x80L) << 32 | (value >>> 28 & 0x7FL | 0x80L) << 24 | (value >>> 35 & 0x7FL | 0x80L) << 16 | (value >>> 42 & 0x7FL | 0x80L) << 8 | value >>> 49;
if ((value & 0xFF00000000000000L) == 0L) {
byteBuf.writeLong(w);
} else if ((value & Long.MIN_VALUE) == 0L) {
byteBuf.writeLong(w);
byteBuf.writeByte((byte) (value >>> 56));
} else {
int w2 = (int) ((value >>> 56 & 0x7FL | 0x80L) << 8 | value >>> 63);
byteBuf.writeLong(w);
byteBuf.writeShort(w2);
}
}
}
}
public static void readUUID(final @NotNull ByteBuf byteBuf) {
byteBuf.readLong(); // least
byteBuf.readLong(); // most
}
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf) {
@@ -82,7 +194,7 @@ public class ProtocolUtil {
public static @NotNull String readString(final ByteBuf byteBuf,
final int cap) throws CorruptedFrameException {
final int length = VarIntUtil.readVarInt(byteBuf);
final int length = readVarInt(byteBuf);
return readString(byteBuf, cap, length);
}
@@ -98,9 +210,9 @@ public class ProtocolUtil {
return str;
}
public static void writeString(final ByteBuf byteBuf, final CharSequence str) {
public static void writeString(final ByteBuf byteBuf, final @NotNull CharSequence str) {
final int size = ByteBufUtil.utf8Bytes(str);
VarIntUtil.writeVarInt(byteBuf, size);
writeVarInt(byteBuf, size);
byteBuf.writeCharSequence(str, StandardCharsets.UTF_8);
}
@@ -118,18 +230,18 @@ public class ProtocolUtil {
public static void writeArray(final ByteBuf byteBuf, final byte @NotNull [] bytes) {
checkFrame(bytes.length < Short.MAX_VALUE, "Too long array");
VarIntUtil.writeVarInt(byteBuf, bytes.length);
writeVarInt(byteBuf, bytes.length);
byteBuf.writeBytes(bytes);
}
public static void writeStringArray(final ByteBuf byteBuf, final String @NotNull [] stringArray) {
VarIntUtil.writeVarInt(byteBuf, stringArray.length);
writeVarInt(byteBuf, stringArray.length);
for (final String s : stringArray) {
writeString(byteBuf, s);
}
}
public static void writeCompoundTag(final ByteBuf byteBuf, final CompoundBinaryTag compoundTag) {
public static void writeCompoundTag(final @NotNull ByteBuf byteBuf, final @NotNull CompoundBinaryTag compoundTag) {
try {
BinaryTagIO.writer().write(compoundTag, (DataOutput) new ByteBufOutputStream(byteBuf));
} catch (IOException exception) {
@@ -192,26 +304,6 @@ public class ProtocolUtil {
return byteBuf.readRetainedSlice(length);
}
public static @NotNull String transformLegacyToModernChannel(@NotNull final String name) {
if (name.indexOf(':') != -1) {
return name;
}
switch (name) {
case REGISTER_CHANNEL_LEGACY:
return REGISTER_CHANNEL;
case UNREGISTER_CHANNEL_LEGACY:
return UNREGISTER_CHANNEL;
case BRAND_CHANNEL_LEGACY:
return BRAND_CHANNEL;
case "BungeeCord":
return "bungeecord:main";
default:
final String lower = name.toLowerCase();
return "legacy:" + INVALID_IDENTIFIER_REGEX.matcher(lower).replaceAll("");
}
}
private void checkFrame(final boolean expression, final String message) {
if (!expression) {
throw new CorruptedFrameException(message);
@@ -1,145 +0,0 @@
/*
* Copyright (C) 2023-2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.CorruptedFrameException;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
@UtilityClass
public class VarIntUtil {
public static int readVarInt(final ByteBuf byteBuf) {
int read = readVarIntSafely(byteBuf);
if (read == Integer.MIN_VALUE) {
throw new CorruptedFrameException("Corrupt VarInt");
}
return read;
}
public static int readVarIntSafely(final @NotNull ByteBuf byteBuf) {
int i = 0;
int maxRead = Math.min(5, byteBuf.readableBytes());
for (int j = 0; j < maxRead; j++) {
int k = byteBuf.readByte();
i |= (k & 0x7F) << j * 7;
if ((k & 0x80) != 128) {
return i;
}
}
return Integer.MIN_VALUE;
}
public static void writeVarInt(final ByteBuf byteBuf, final int value) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
// that the proxy will write, to improve inlining.
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
byteBuf.writeShort(w);
} else {
writeVarIntFull(byteBuf, value);
}
}
private void writeVarIntFull(final ByteBuf byteBuf, final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
if ((value & (0xFFFFFFFF << 7)) == 0) {
byteBuf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
byteBuf.writeShort(w);
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
byteBuf.writeMedium(w);
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
byteBuf.writeInt(w);
} else {
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
byteBuf.writeInt(w);
byteBuf.writeByte(value >>> 28);
}
}
public static void writeVarLong(final ByteBuf byteBuf, final long value) {
// Peel the one and two byte count cases explicitly as they are the most common VarLong sizes
// that the proxy will write, to improve inlining.
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
byteBuf.writeByte((byte) value);
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 8 | value >>> 7);
byteBuf.writeShort(w);
} else {
writeVarLongFull(byteBuf, value);
}
}
private void writeVarLongFull(final ByteBuf byteBuf, final long value) {
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
byteBuf.writeByte((byte) value);
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 8 | value >>> 7);
byteBuf.writeShort(w);
} else if ((value & 0xFFFFFFFFFFE00000L) == 0L) {
int w = (int) ((value & 0x7FL | 0x80L) << 16 | (value >>> 7 & 0x7FL | 0x80L) << 8 | value >>> 14);
byteBuf.writeMedium(w);
} else if ((value & 0xFFFFFFFFF0000000L) == 0L) {
int w =
(int) ((value & 0x7FL | 0x80L) << 24 | (value >>> 7 & 0x7FL | 0x80L) << 16 | (value >>> 14 & 0x7FL | 0x80L) << 8 | value >>> 21);
byteBuf.writeInt(w);
} else {
long l =
(value & 0x7FL | 0x80L) << 24 | (value >>> 7 & 0x7FL | 0x80L) << 16 | (value >>> 14 & 0x7FL | 0x80L) << 8 | (value >>> 21 & 0x7FL | 0x80L);
if ((value & 0xFFFFFFF800000000L) == 0L) {
int w =
(int) l;
byteBuf.writeInt(w);
byteBuf.writeByte((int) (value >>> 28));
} else if ((value & 0xFFFFFC0000000000L) == 0L) {
int w =
(int) l;
int w2 = (int) ((value >>> 28 & 0x7FL | 0x80L) << 8 | value >>> 35);
byteBuf.writeInt(w);
byteBuf.writeShort(w2);
} else if ((value & 0xFFFE000000000000L) == 0L) {
int w =
(int) l;
int w2 = (int) ((value >>> 28 & 0x7FL | 0x80L) << 16 | (value >>> 35 & 0x7FL | 0x80L) << 8 | value >>> 42);
byteBuf.writeInt(w);
byteBuf.writeMedium(w2);
} else {
long w =
(value & 0x7FL | 0x80L) << 56 | (value >>> 7 & 0x7FL | 0x80L) << 48 | (value >>> 14 & 0x7FL | 0x80L) << 40 | (value >>> 21 & 0x7FL | 0x80L) << 32 | (value >>> 28 & 0x7FL | 0x80L) << 24 | (value >>> 35 & 0x7FL | 0x80L) << 16 | (value >>> 42 & 0x7FL | 0x80L) << 8 | value >>> 49;
if ((value & 0xFF00000000000000L) == 0L) {
byteBuf.writeLong(w);
} else if ((value & Long.MIN_VALUE) == 0L) {
byteBuf.writeLong(w);
byteBuf.writeByte((byte) (value >>> 56));
} else {
int w2 = (int) ((value >>> 56 & 0x7FL | 0x80L) << 8 | value >>> 63);
byteBuf.writeLong(w);
byteBuf.writeShort(w2);
}
}
}
}
}
@@ -56,9 +56,9 @@ database:
maximum-age: 5
# Každý nový login bude přidán do řady kvuli prevenci spam útoků
# Řada je aktulizována každých 500 milisekund (10 ticků)
# Řada je aktulizována každých 1 sekund (20 ticků)
queue:
# Maximum součastných aktualizací řady za 500 milisekund
# Maximum součastných aktualizací řady za 1 sekund
max-polls: 30
# Každý připojený hráč bude přepojen do
@@ -89,12 +89,12 @@ verification:
# - ADVENTURE: všechny komponenty UI jsou zobrazeny
gamemode: ADVENTURE
# Checks if the client sends proper packets when mounting a vehicle
# Kontroluje, zda klient odesílá správné pakety při montáži vozidla
vehicle:
# Possible types: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: New players will always be mounted on a vehicle
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
# - NEVER: New players will never be mounted on a vehicle (Recommended)
# Typy: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Noví hráči budou vždy namontováni na vozidle (Doporučeno)
# - DURING_ATTACK: Noví hráči budou namontováni na vozidlo pouze během útoku
# - NEVER: Noví hráči nebudou nikdy namontováni na vozidlo
timing: ALWAYS
# Nechá hráče verifikovat pomocí kódu na mapě ve hře
@@ -105,39 +105,51 @@ verification:
# - DURING_ATTACK: Noví hráči dostanou map captchu jenom při útoku
# - NEVER: Noví hráči nikdy nedostanou map CAPTCHU (Doporučeno)
timing: NEVER
# Options for the effects on the image shown to the player
# Možnosti pro efekty na zobrazeném obrázku
# Toto byste měli upravovat, pouze pokud víte, co děláte
effects:
# Adds a glow effect to the image
flare: true
# Adds random lines to the image
# Přidá do obrázku náhodné čáry
scratches: true
# Applies a ripple (sine) distortion filter
# Přidá filtr zvlnění (vlnitý efekt)
ripple: true
# Applies a horizontal distortion filter
smear: true
# Adds a bit of blurriness/distortion to the image
pinch: true
# Applies color correction (color saturation)
saturation: 0.15
# Applies a horizontal triangular ripple filter
distortion: 2
# Přidá nárazový filtr (zaostřený text)
bump: true
# Filtr zkreslení
distortion:
# Měl by Sonar použít filtr zkreslení?
enabled: true
# Jaký tvar by měl Sonar použít pro filtr zkreslení?
# Typy: 0, 1, 2, 3
# - 0: Kříže
# - 1: Čáry
# - 2: Kruhy
# - 3: Čtverce
shape: 2
# Vzdálenost mezi zkreslenými pixely/objekty
distance: 5
# Hustota zkreslených pixelů/objektů
density: 0.1
# Míra prolnutí mezi barvami objektů
mix: 0.15
# Cesta k obrázku na pozadí, který Sonar používá pro CAPTCHA
# Pokud není obrázek nalezen, bude jako výchozí pozadí použit šum
# Zde můžete jednoduše zadat název souboru, pokud je soubor v adresáři pluginu
background: ''
# Měl by Sonar automaticky detekovat barvu pozadí?
# To se provádí za účelem vytvoření kontrastu mezi obrázkem na pozadí a textem
# Pokud je toto zakázáno, Sonar použije pro barvy náhodný přechod
auto-color: false
# Kolik odpovědí by měl Sonar předgenerovat?
# Tento úkol běží souběžně v pozadí
precompute: 500
# Jak dlouho by měl Sonar čekat než hráč selže CAPTCHU?
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
max-duration: 45000
max-duration: 30000
# Kolikrát musí hráč selhat CAPTCHU před celkovém selhání verifikace?
max-tries: 3
# Znaky (čísla a písmena), které jsou povoleny k zobrazování v CAPTCHE
# It is not recommended to add numbers or letters that might look like one another
# Nedoporučuje se přidávat čísla nebo písmena, která by se mohla navzájem podobat
dictionary: '1235689'
# Jaké typy fontů by měl Sonar používat pro map CAPTCHU?
font-names:
- Dialog
- DialogInput
- SansSerif
- Monospaced
# Zkouší pokuď hráč posílá správný "client brand packet" serveru
client-brand:
@@ -148,9 +160,9 @@ verification:
# Maximální délka "client brand" při verifikaci
max-length: 64
# Regex for validování jmen při verifikaci
# Regex pro validování jmen při verifikaci
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex for validaci "client locale" při verifikaci
# Regex pro validaci "client locale" při verifikaci
valid-locale-regex: ^[a-zA-Z_]+$
# Maximální odezva hráče aby byl odpojen
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
@@ -158,6 +170,24 @@ verification:
# Maximální počet login packetů, který musí hřáč poslat aby byl vyhozen
max-login-packets: 256
# Je-li povoleno, přehrávač bude přenesen zpět na původní server
# po úspěšném absolvování ověření robota.
# Tuto funkci představil Mojang ve verzi Minecraft 1.20.5
transfer:
# Měl by Sonar přenést hráče na původní server (místo toho, aby je kopl)?
# Aby to fungovalo, musíte tuto funkci povolit v konfiguraci vašeho serveru
# Kromě toho možná budete chtít snížit množství omezení rychlosti přihlášení
# provádí Velocity nebo jiné proxy/pluginy, protože to může zabránit
# hráč nebyl převeden správně.
enabled: false
# Na který server by měl Sonar přenést přehrávač, když projde ověřením?
# Zadejte IP serveru, který hráči používají k běžnému připojení k vašemu serveru.
# Například můžete zadat "mc.hypixel.net" nebo přímou IP jako "1.1.1.1"
destination-host: "play.my-server.com"
# Jaký port by měl Sonar použít při přenosu přehrávače na původní server?
# Pokud váš server nepotřebuje port pro připojení, můžete toto ponechat jako 25565.
destination-port: 25565
# Měl by Sonar posílat nové verifikace?
log-connections: true
# Měl by Sonar posílat nové verifikace při útoku?
@@ -165,13 +195,17 @@ verification:
# Měl by Sonar posílat každý jeden pohyb při verifikaci?
# Toto nastavení není doporučeno pro servry v produkci, ale může být nápomocné při vyhledávaní chyb
debug-xyz-positions: false
# Měl by Sonar také kontrolovat hráče Geyser (Bedrock)?
# Tato funkce je experimentální a může způsobit problémy
# Pokud je toto zakázáno, hráči Bedrock budou přeskočeni
check-geyser-players: true
# Množství času který musí uběhnout aby hráč byl odpojen
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
read-timeout: 3500
# How long should a player wait before reconnecting during verification Jak dlouho by měl hráč
read-timeout: 8000
# Jak dlouho by měl hráč čekat, než se při ověřování znovu připojí
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
rejoin-delay: 8000
# How long should Sonar remember the amount of failed verifications for a player?
# Jak dlouho by si měl Sonar pamatovat počet neúspěšných ověření hráče?
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
remember-time: 120000
# Jak dlouho by měla být IP adresa zakázána k připojení po selhání verifikace mockrát?
@@ -53,9 +53,9 @@ database:
maximum-age: 5
# Jede neue Login-Anfrage wird in eine Warteschlange eingereiht, um Spam-Beitrittsangriffe zu vermeiden
# Die Warteschlange wird alle 500 Millisekunden (10 Ticks) aktualisiert
# Die Warteschlange wird alle 1 Sekunde (20 Ticks) aktualisiert
queue:
# Maximale Anzahl an gleichzeitigen Abfragen der Warteschlange pro 500 Millisekunden
# Maximale Anzahl an gleichzeitigen Abfragen der Warteschlange pro 1 Sekunde
max-polls: 30
# Jeder neue Spieler, der das erste Mal beitritt, wird zu
@@ -86,12 +86,12 @@ verification:
# - ADVENTURE: alle UI-Komponenten sind sichtbar
gamemode: ADVENTURE
# Checks if the client sends proper packets when mounting a vehicle
# Überprüft, ob der Spieler beim Einsteigen in ein Boot die richtigen Pakete sendet
vehicle:
# Possible types: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: New players will always be mounted on a vehicle
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
# - NEVER: New players will never be mounted on a vehicle (Recommended)
# Mögliche Typen: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Neue Spieler werden immer auf einem Fahrzeug montiert (Empfohlen)
# - DURING_ATTACK: Neue Spieler werden nur während eines Angriffs auf ein Fahrzeug gesetzt
# - NEVER: Neue Spieler werden niemals in ein Fahrzeug eingebaut
timing: ALWAYS
# Lässt den Spieler einen Code von einer virtuellen Karte im Chat eingeben
@@ -102,39 +102,51 @@ verification:
# - DURING_ATTACK: Neue Spieler erhalten nur während eines Angriffs ein CAPTCHA
# - NEVER: Neue Spieler erhalten nie ein CAPTCHA (Empfohlen)
timing: NEVER
# Options for the effects on the image shown to the player
# Optionen für die Effekte auf dem angezeigten Bild
# Sie sollten dies nur bearbeiten, wenn Sie wissen, was Sie tun
effects:
# Adds a glow effect to the image
flare: true
# Adds random lines to the image
# Fügt dem Bild zufällige Linien hinzu
scratches: true
# Applies a ripple (sine) distortion filter
# Wendet einen Ripple-Filter an (Welleneffekt)
ripple: true
# Applies a horizontal distortion filter
smear: true
# Adds a bit of blurriness/distortion to the image
pinch: true
# Applies color correction (color saturation)
saturation: 0.15
# Applies a horizontal triangular ripple filter
distortion: 2
# Wendet einen Bump-Filter an (geschärfter Text)
bump: true
# Verzerrungsfilter
distortion:
# Sollte Sonar einen Verzerrungsfilter anwenden?
enabled: true
# Welche Form sollte Sonar für den Verzerrungsfilter verwenden?
# Mögliche Typen: 0, 1, 2, 3
# - 0: Kreuze
# - 1: Linien
# - 2: Kreise
# - 3: Quadrate
shape: 2
# Abstand zwischen verzerrten Pixeln/Objekten
distance: 5
# Dichte der verzerrten Pixel/Objekte
density: 0.1
# Ausmaß der Vermischung zwischen den Farben von Objekten
mix: 0.15
# Pfad zum Hintergrundbild, das Sonar für das CAPTCHA verwendet
# Wenn das Bild nicht gefunden wird, wird ein Rauschen als Standardhintergrund verwendet
# Sie können hier einfach den Namen der Datei eingeben, wenn sich die Datei im Plugin-Verzeichnis befindet
background: ''
# Sollte Sonar die Hintergrundfarbe automatisch erkennen?
# Dies geschieht, um einen Kontrast zwischen dem Hintergrundbild und dem Text zu erzeugen
# Wenn dies deaktiviert ist, verwendet Sonar einen zufälligen Farbverlauf für die Farben
auto-color: false
# Wie viele Antworten soll Sonar vorbereiten?
# Dieser Vorgang erfolgt asynchron im Hintergrund
precompute: 500
# Wie lange soll Sonar warten, bis der Spieler das CAPTCHA nicht besteht?
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
max-duration: 45000
max-duration: 30000
# Wie oft muss ein Spieler das CAPTCHA nicht bestehen, bevor er die Überprüfung nicht besteht?
max-tries: 3
# Zeichen (Buchstaben und Zahlen), die im Antwort-CAPTCHA erscheinen dürfen
# It is not recommended to add numbers or letters that might look like one another
# Es wird nicht empfohlen, Zahlen oder Buchstaben hinzuzufügen, die einander ähneln könnten
dictionary: '1235689'
# Welche Schriftarten soll Sonar für die Map-CAPTCHA-Codes verwenden?
font-names:
- Dialog
- DialogInput
- SansSerif
- Monospaced
# Überprüft, ob die Spieler eine gültige Client-Marke an den Server senden
client-brand:
@@ -155,6 +167,24 @@ verification:
# Maximale Anzahl an Login-Paketen, die der Spieler senden muss, um gekickt zu werden
max-login-packets: 256
# Wenn aktiviert, wird der Player zurück zum Ursprungsserver gesendet
# falls dieser die der Bot-Verifizierung erfolgreich besteht.
# Diese Funktion wurde von Mojang in Minecraft Version 1.20.5 eingeführt
transfer:
# Sollte Sonar den Spieler auf den Ursprungsserver senden (anstatt ihn zu kicken)?
# Damit dies funktioniert, müssen Sie die Funktion in der Konfiguration Ihres Servers aktivieren
# Darüber hinaus möchten Sie möglicherweise die Begrenzung der Anmelderate reduzieren
# wird von Velocity oder anderen Proxys/Plugins durchgeführt, da dies das verhindern könnte
# Spieler konnte nicht korrekt übertragen werden.
enabled: false
# Auf welchen Server soll Sonar den Spieler übertragen, wenn die Verifizierung bestanden wurde?
# Bitte geben Sie die Server-IP ein, die von Spielern verwendet wird, um sich zu verbinden.
# Sie können beispielsweise "mc.hypixel.net" oder eine direkte IP wie "1.1.1.1" verwenden.
destination-host: "play.my-server.com"
# Welchen Port sollte Sonar verwenden, wenn der Spieler auf den Ursprungsserver gesendet wird?
# Wenn Ihr Server keinen Port zum Herstellen einer Verbindung benötigt, können Sie diesen auf 25565 belassen.
destination-port: 25565
# Soll Sonar neue Überprüfungsversuche protokollieren?
log-connections: true
# Soll Sonar neue Überprüfungsversuche während Angriffen protokollieren?
@@ -162,9 +192,13 @@ verification:
# Soll Sonar jede einzelne Bewegung/Positionsänderung während der Überprüfung protokollieren?
# Dies wird für Produktivserver nicht empfohlen, kann aber beim Auffinden von Fehlern hilfreich sein.
debug-xyz-positions: false
# Sollte Sonar auch Geyser (Bedrock)-Spieler überprüfen?
# Diese Funktion ist experimentell und kann Probleme verursachen
# Wenn dies deaktiviert ist, werden Bedrock-Spieler übersprungen
check-geyser-players: true
# Zeit, die verstreichen muss, bevor ein Spieler ein Timeout erleidet
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
read-timeout: 3500
read-timeout: 8000
# Wie lange sollte ein Spieler warten, bevor er während der Überprüfung erneut verbinden kann
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
rejoin-delay: 8000
@@ -28,7 +28,7 @@ attack-tracker:
# Amount of time that has to pass in order for an attack to be over
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
min-attack-duration: 30000
# Number of times an incident has to be reported in order to be acknowledged as an attack
# Number of times an incident has to be reported to be acknowledged as an attack
# This number acts as a buffer to filter out false attack notifications
min-attack-threshold: 2
# Amount of time that has to pass in order for a new attack to be detected
@@ -55,9 +55,9 @@ database:
maximum-age: 5
# Every new login request will be queued to avoid spam join attacks
# The queue is updated every 500 milliseconds (10 ticks)
# The queue is updated every 1 second (20 ticks)
queue:
# Maximum number of concurrent queue polls per 500 milliseconds
# Maximum number of concurrent queue polls per 1 second
max-polls: 30
# Every new player that joins for the first time will be sent to
@@ -71,7 +71,7 @@ verification:
timing: ALWAYS
checks:
# Checks if the players' falling motion is following Minecraft's gravity formula
# All predicted motions are precalculated in order to save performance
# All predicted motions are precalculated to save performance
gravity:
# Should Sonar check for valid client gravity? (Recommended)
enabled: true
@@ -79,7 +79,7 @@ verification:
check-collisions: true
# Should Sonar make the player solve a CAPTCHA if they fail the gravity check?
captcha-on-fail: false
# Maximum number of ticks the player has to fall in order to be allowed to hit the platform
# Maximum number of ticks the player has to fall to be allowed to hit the platform
max-movement-ticks: 8
# The gamemode of the player during verification
# Possible types: SURVIVAL, CREATIVE, ADVENTURE
@@ -91,9 +91,9 @@ verification:
# Checks if the client sends proper packets when mounting a vehicle
vehicle:
# Possible types: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: New players will always be mounted on a vehicle
# - ALWAYS: New players will always be mounted on a vehicle (Recommended)
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
# - NEVER: New players will never be mounted on a vehicle (Recommended)
# - NEVER: New players will never be mounted on a vehicle
timing: ALWAYS
# Make the player type a code from a virtual map in chat
@@ -105,40 +105,52 @@ verification:
# - NEVER: New players will never receive a CAPTCHA (Recommended)
timing: NEVER
# Options for the effects on the image shown to the player
# You should only edit this if you know what you're doing
effects:
# Adds a glow effect to the image
flare: true
# Adds random lines to the image
scratches: true
# Applies a ripple (sine) distortion filter
# Applies a ripple filter (wavy effect)
ripple: true
# Applies a horizontal distortion filter
smear: true
# Adds a bit of blurriness/distortion to the image
pinch: true
# Applies color correction (color saturation)
saturation: 0.15
# Applies a horizontal triangular ripple filter
distortion: 2
# Applies a bump filter (sharpened text)
bump: true
# Distortion filter
distortion:
# Should Sonar apply a distortion filter?
enabled: true
# What shape should Sonar use for the distortion filter?
# Possible types: 0, 1, 2, 3
# - 0: Crosses
# - 1: Lines
# - 2: Circles
# - 3: Squares
shape: 2
# Distance between distorted pixels/objects
distance: 5
# Density of the distorted pixels/objects
density: 0.1
# Amount of blending between the colors of objects
mix: 0.15
# Path to the background image Sonar uses for the CAPTCHA
# If the image isn't found, a noise will be used as the default background
# You can put the name of the file here if the file is in the plugin directory
background: ''
# Should Sonar automatically detect the background color?
# This is done to create a contrast between the background image and the text
# If this is disabled, Sonar will use a random gradient for the colors
auto-color: false
# How many answers should Sonar prepare?
# This task happens asynchronously in the background
precompute: 500
# How long should Sonar wait until the player fails the CAPTCHA?
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
max-duration: 45000
max-duration: 30000
# How many times must a player fail the CAPTCHA before failing the verification?
max-tries: 3
# Characters (letters and numbers) that are allowed to appear in the answer to the CAPTCHA
# It is not recommended to add numbers or letters that might look like one another
dictionary: '1235689'
# Which fonts should Sonar use for the map CAPTCHA codes?
font-names:
- Dialog
- DialogInput
- SansSerif
- Monospaced
# Checks if the players is sending a valid client brand to the server
# Checks if the players are sending a valid client brand to the server
client-brand:
# Should Sonar check for valid client brands? (Recommended)
enabled: true
@@ -151,12 +163,30 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex for validating client locale during verification
valid-locale-regex: ^[a-zA-Z_]+$
# Ping a player has to have in order to timeout
# Ping a player has to have to timeout
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
max-ping: 10000
# Maximum number of login packets the player has to send in order to be kicked
max-login-packets: 256
# If enabled, the player will be transferred back to the origin server
# after successfully passing the bot verification.
# This feature was introduced by Mojang in Minecraft version 1.20.5
transfer:
# Should Sonar transfer the player to the origin server (instead of kicking them)?
# For this to work, you must enable the feature in your server's configuration
# Additionally, you might want to reduce the amount of login rate-limiting
# performed by Velocity or other proxies/plugins, as this might prevent the
# player from being transferred correctly.
enabled: false
# Which server should Sonar transfer the player to when the verification is passed?
# Please enter the server IP that is used by players to normally connect to your server.
# For example, you can put "mc.hypixel.net" or a direct IP like "1.1.1.1"
destination-host: "play.my-server.com"
# Which port should Sonar use when transferring the player to the origin server?
# If your server does not need a port to connect to, you can leave this as 25565.
destination-port: 25565
# Should Sonar log new verification attempts?
log-connections: true
# Should Sonar log new verification attempts during attacks?
@@ -164,9 +194,13 @@ verification:
# Should Sonar log every single movement/position change during verification?
# This is not recommended for production servers but can be helpful for spotting errors.
debug-xyz-positions: false
# Should Sonar also check Geyser (Bedrock) players?
# This feature is experimental and might cause issues
# If this is disabled, Bedrock players will be skipped
check-geyser-players: true
# Amount of time that has to pass before a player times out
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
read-timeout: 3500
read-timeout: 8000
# How long should a player wait before reconnecting during verification
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
rejoin-delay: 8000
@@ -0,0 +1,222 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
#
# Paramètre général de Sonar
general:
# Devrait Sonar enregistrer les adresses IP des joueurs dans la console ?
log-player-addresses: true
# Nombre maximum de joueurs en ligne avec la même IP
max-online-per-ip: 3
# Paramètre de détection d'attaque
# Modifier uniquement si vous savez ce que vous faites
attack-tracker:
# Nombre minimum de nouveaux joueurs pour qu'une attaque soit détectée
min-players-for-attack: 8
# Durée nécessaire pour qu'une attaque soit considérée comme terminée
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
min-attack-duration: 30000
# Nombre de fois qu'un incident doit être signalé pour être reconnu comme une attaque
# Ce nombre sert de tampon pour filtrer les fausses notifications d'attaque
min-attack-threshold: 2
# Durée nécessaire pour qu'une nouvelle attaque soit détectée
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
attack-cooldown-delay: 3000
# Vous pouvez connecter Sonar à une base de données pour conserver les joueurs vérifiés même après le redémarrage de votre serveur
# Remarque : Les adresses IP sont enregistrées en texte brut. Vous êtes responsable de la sécurité de votre base de données !
database:
# Type de base de données utilisé par Sonar pour stocker les joueurs vérifiés
# Types possibles : NONE, MYSQL, MARIADB
type: NONE
# Adresse de l'hôte pour l'authentification avec la base de données SQL
host: localhost
# Port pour l'authentification avec la base de données SQL
port: 3306
# Nom de la base de données SQL
name: sonar
# Nom d'utilisateur pour l'authentification avec la base de données SQL
username: ''
# Mot de passe pour l'authentification avec la base de données SQL
password: ''
# Pendant combien de jours Sonar doit-il conserver les joueurs vérifiés dans la base de données ?
maximum-age: 5
# Chaque nouvelle demande de connexion sera mise en file d'attente pour éviter les attaques d'inscription par spam
# La file d'attente est mise à jour toutes les 1 seconde (20 ticks)
queue:
# Nombre maximum de demandes de connexion en file d'attente simultanées par seconde
max-polls: 30
# Chaque nouveau joueur qui se connecte pour la première fois sera envoyé à
# un serveur limbo léger où des vérifications avancées de bot sont effectuées
verification:
# Quand Sonar devrait-il vérifier les nouveaux joueurs ?
# Types possibles : ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS : Les nouveaux joueurs seront toujours vérifiés (Recommandé)
# - DURING_ATTACK : Les nouveaux joueurs ne seront vérifiés que pendant une attaque
# - NEVER : Les nouveaux joueurs ne seront jamais vérifiés
timing: ALWAYS
checks:
# Vérifie si le mouvement de chute des joueurs suit la formule de la gravité de Minecraft
# Tous les mouvements prévus sont précalculés afin d'économiser les performances
gravity:
# Sonar devrait-il vérifier la gravité du client ? (Recommandé)
enabled: true
# Sonar devrait-il vérifier si le joueur entre en collision avec des blocs ? (Recommandé)
check-collisions: true
# Sonar devrait-il faire résoudre un CAPTCHA au joueur s'il échoue à la vérification de la gravité ?
captcha-on-fail: false
# Nombre maximum de ticks que le joueur a pour tomber avant de pouvoir toucher la plateforme
max-movement-ticks: 8
# Le mode de jeu du joueur pendant la vérification
# Types possibles : SURVIVAL, CREATIVE, ADVENTURE
# - SURVIVAL : tous les composants de l'interface utilisateur sont visibles
# - CREATIVE : la santé et la faim sont cachées
# - ADVENTURE : tous les composants de l'interface utilisateur sont visibles
gamemode: ADVENTURE
# Vérifie si le client envoie les paquets appropriés lorsqu'il monte dans un véhicule
vehicle:
# Types possibles : ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS : Les nouveaux joueurs seront toujours montés dans un véhicule (Recommandé)
# - DURING_ATTACK : Les nouveaux joueurs ne seront montés dans un véhicule que pendant une attaque
# - NEVER : Les nouveaux joueurs ne seront jamais montés dans un véhicule
timing: ALWAYS
# Fait taper au joueur un code à partir d'une carte virtuelle dans le chat
map-captcha:
# Quand Sonar devrait-il faire résoudre un CAPTCHA au joueur ?
# Types possibles : ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS : Les nouveaux joueurs recevront toujours un CAPTCHA
# - DURING_ATTACK : Les nouveaux joueurs ne recevront un CAPTCHA que pendant une attaque
# - NEVER : Les nouveaux joueurs ne recevront jamais de CAPTCHA (Recommandé)
timing: NEVER
# Options pour les effets sur l'image montrée au joueur
# Vous ne devriez modifier cela que si vous savez ce que vous faites
effects:
# Ajoute des lignes aléatoires à l'image
scratches: true
# Applique un filtre d'ondulation (effet ondulé)
ripple: true
# Applique un filtre de bosse (texte accentué)
bump: true
# Filtre de distorsion
distortion:
# Sonar devrait-il appliquer un filtre de distorsion ?
enabled: true
# Quelle forme Sonar devrait-il utiliser pour le filtre de distorsion ?
# Types possibles : 0, 1, 2, 3
# - 0 : Croix
# - 1 : Lignes
# - 2 : Cercles
# - 3 : Carrés
shape: 2
# Distance entre les pixels/objets déformés
distance: 5
# Densité des pixels/objets déformés
density: 0.1
# Quantité de mélange entre les couleurs des objets
mix: 0.15
# Chemin vers l'image d'arrière-plan que Sonar utilise pour le CAPTCHA
# Si l'image n'est pas trouvée, un bruit sera utilisé comme arrière-plan par défaut
# Vous pouvez simplement mettre le nom du fichier ici si le fichier est dans le répertoire du plugin
background: ''
# Sonar devrait-il détecter automatiquement la couleur de fond ?
# Cela est fait pour créer un contraste entre l'image d'arrière-plan et le texte
# Si cela est désactivé, Sonar utilisera un dégradé aléatoire pour les couleurs
auto-color: false
# Combien de réponses Sonar devrait-il préparer ?
# Cette tâche se déroule de manière asynchrone en arrière-plan
precompute: 500
# Combien de temps Sonar devrait-il attendre jusqu'à ce que le joueur échoue le CAPTCHA ?
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
max-duration: 30000
# Combien de fois un joueur doit-il échouer au CAPTCHA avant d'échouer la vérification ?
max-tries: 3
# Caractères (lettres et chiffres) autorisés à apparaître dans la réponse au CAPTCHA
# Il n'est pas recommandé d'ajouter des chiffres ou des lettres qui pourraient se ressembler
dictionary: '1235689'
# Vérifie si le joueur envoie une marque client valide au serveur
client-brand:
# Sonar devrait-il vérifier les marques client valides ? (Recommandé)
enabled: true
# Regex pour valider les marques client lors de la vérification
valid-regex: ^[!-~ ]+$
# Longueur maximale de la marque client lors de la vérification
max-length: 64
# Regex pour valider les noms d'utilisateur lors de la vérification
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex pour valider la locale client lors de la vérification
valid-locale-regex: ^[a-zA-Z_]+$
# Le ping qu'un joueur doit avoir pour dépasser le délai d'attente
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
max-ping: 10000
# Nombre maximal de paquets de connexion que le joueur doit envoyer pour être expulsé
max-login-packets: 256
# S'il est activé, le joueur sera renvoyé au serveur d'origine
# après avoir passé avec succès la vérification du bot.
# Cette fonctionnalité a été introduite par Mojang dans la version Minecraft 1.20.5
transfer:
# Sonar devrait-il transférer le joueur au serveur d'origine (au lieu de l'expulser) ?
# Pour que cela fonctionne, vous devez activer la fonctionnalité dans la configuration de votre serveur
# De plus, vous voudrez peut-être réduire la quantité de limitation de débit de connexion
# effectuée par Velocity ou d'autres proxys/plugins, car cela pourrait empêcher le
# joueur d'être transféré correctement.
enabled: false
# Quel serveur Sonar devrait-il utiliser pour transférer le joueur une fois la vérification réussie ?
# Veuillez entrer l'adresse IP du serveur que les joueurs utilisent normalement pour se connecter à votre serveur.
# Par exemple, vous pouvez mettre "mc.hypixel.net" ou une IP directe comme "1.1.1.1"
destination-host: "play.my-server.com"
# Quel port Sonar devrait-il utiliser pour transférer le joueur au serveur d'origine ?
# Si votre serveur n'a pas besoin d'un port pour se connecter, vous pouvez laisser celui-ci comme 25565.
destination-port: 25565
# Sonar devrait-il journaliser les nouvelles tentatives de vérification ?
log-connections: true
# Sonar devrait-il journaliser les nouvelles tentatives de vérification pendant les attaques ?
log-during-attack: false
# Sonar devrait-il journaliser chaque changement de position/mouvement pendant la vérification ?
# Ce n'est pas recommandé pour les serveurs de production mais peut être utile pour repérer les erreurs.
debug-xyz-positions: false
# Sonar devrait-il également vérifier les joueurs Geyser (Bedrock) ?
# Cette fonctionnalité est expérimentale et peut causer des problèmes
# Si cela est désactivé, les joueurs Bedrock seront ignorés
check-geyser-players: true
# Durée avant qu'un joueur ne soit déconnecté
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
read-timeout: 8000
# Combien de temps un joueur doit-il attendre avant de se reconnecter pendant la vérification
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
rejoin-delay: 8000
# Combien de temps Sonar doit-il se souvenir du nombre de vérifications échouées pour un joueur ?
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
remember-time: 120000
# Combien de temps une adresse IP doit-elle être interdite de connexion en cas d'échec trop fréquent de la vérification ?
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
blacklist-time: 600000
# Combien de fois un joueur doit-il échouer à la vérification pour être placé sur liste noire ?
# (Réglez cette valeur à 0 pour désactiver la liste noire entièrement)
blacklist-threshold: 2
# Liste des identifiants de protocole qui ne sont pas vérifiés par Sonar (bypass de vérification)
# Vous pouvez trouver la liste complète de tous les identifiants de protocole ici :
# https://wiki.vg/Protocol_version_numbers
# Par exemple, Minecraft 1.20 a l'ID 763.
whitelisted-protocols: []
# Liste des identifiants de protocole qui ne peuvent pas du tout rejoindre le serveur
blacklisted-protocols: []
@@ -55,9 +55,9 @@ database:
maximum-age: 5
# ყოველი ახალი შესვლის მოთხოვნა იქნება რიგში, რათა თავიდან აიცილოთ სპამის მიერთების შეტევები
# რიგი განახლდება ყოველ 500 მილიწამში (10 ტიკი)
# რიგი განახლდება ყოველ 1000 მილიწამში (20 ტიკი)
queue:
# ახალი მოთამაშეების მაქსიმალური რაოდენობა, რომლებსაც შეუძლიათ შესვლა სერვერზე ყოველ 500 მილიწამში
# ახალი მოთამაშეების მაქსიმალური რაოდენობა, რომლებსაც შეუძლიათ შესვლა სერვერზე ყოველ 1000 მილიწამში
max-polls: 30
# ყოველი ახალი მოთამაშე, რომელიც პირველად შეუერთდება, გაიგზავნება
@@ -66,7 +66,7 @@ verification:
# როდის უნდა გადაამოწმოს Sonar-მა ახალი მოთამაშეები?
# შესაძლო ტიპები: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: ახალი მოთამაშეები ყოველთვის მოწმდება (რეკომენდებულია)
# - DURING_ATTACK: ახალი მოთამაშეები შემოწმდებიან მხოლოდ შეტევის დროს
# - DURING_ATTACK: ახალი მოთამაშეები შემოწმდებიან მხოლოდ შეტევის დროსდროს
# - NEVER: ახალი მოთამაშეები არასოდეს შემოწმდებიან
timing: ALWAYS
checks:
@@ -88,12 +88,12 @@ verification:
# - ADVENTURE: ყველა UI კომპონენტი ჩანს
gamemode: ADVENTURE
# Checks if the client sends proper packets when mounting a vehicle
# ამოწმებს, აგზავნის თუ არა კლიენტი შესაბამის პაკეტებს სატრანსპორტო საშუალებაზე დაჯდომის დროს
vehicle:
# Possible types: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: New players will always be mounted on a vehicle
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
# - NEVER: New players will never be mounted on a vehicle (Recommended)
# შესაძლო ტიპები: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: ახალი მოთამაშეები ყოველთვის დაჯდებიან სატრანსპორტო საშუალებაზე
# - DURING_ATTACK: ახალი მოთამაშეები მხოლოდ ბოტების შემოტევის დროს დაჯდებიან სატრანსპორტო საშუალებაზე
# - NEVER: ახალი მოთამაშეები არასოდეს დაჯდებიან სატრანსპორტო საშუალებაზე (რეკომენდებულია)
timing: ALWAYS
# დააწერინეთ მოთამაშეს კოდი ჩატში, რომელიც იქნება ნაჩვენები ვირტუალურ მაპზე, რომელიც ხელში ეჭირებად
@@ -104,41 +104,53 @@ verification:
# - DURING_ATTACK: ახალი მოთამაშებს მოუწევთ CAPTCHA-ს გაკეთება მხოლოდ შეტევის დროს
# - NEVER: ახალ მოთამაშეებს არასდროს მოუწევთ CAPTCHA-ს გაკეთება (Recommended)
timing: NEVER
# Options for the effects on the image shown to the player
# ეფექტების პარამეტრები გამოსახულებაზე, რომელიც აჩვენა მოთამაშეს
# თქვენ უნდა შეცვალოთ ეს მხოლოდ იმ შემთხვევაში, თუ იცით, რას აკეთებთ
effects:
# Adds a glow effect to the image
flare: true
# Adds random lines to the image
# მატებს შემთხვევითი ხაზების ეფექტს გამოსახულებაზე
scratches: true
# Applies a ripple (sine) distortion filter
# აყენებს ტალღური ეფექტის ფილტრს (ტალღისებური ეფექტი)
ripple: true
# Applies a horizontal distortion filter
smear: true
# Adds a bit of blurriness/distortion to the image
pinch: true
# Applies color correction (color saturation)
saturation: 0.15
# Applies a horizontal triangular ripple filter
distortion: 2
# აყენებს ამობურცული ტექსტის ფილტრს (გამხრწნილი ტექსტი)
bump: true
# დამახინჯების ფილტრი
distortion:
# უნდა დაამატოს თუ არა Sonar-მა დამახინჯების ფილტრი?
enabled: true
# რა ფორმას უნდა გამოიყენოს Sonar-მა დამახინჯების ფილტრისთვის?
# შესაძლო ტიპები: 0, 1, 2, 3
# - 0: ჯვრები
# - 1: ხაზები
# - 2: წრეები
# - 3: კვადრატები
shape: 2
# მანძილი დამახინჯებულ პიქსელებს/ობიექტებს შორის
distance: 5
# დამახინჯებული პიქსელების/ობიექტების სიხშირე
density: 0.1
# შერევის ხარისხი ობიექტების ფერებს შორის
mix: 0.15
# CAPTCHA-სთვის Sonar-ის მიერ გამოყენებული ფონური გამოსახულების ბილიკი
# თუ გამოსახულება არ მოიძებნა, ფონად გამოყენებული იქნება noise ი
# შეგიძლიათ უბრალოდ ჩაწეროთ ფაილის სახელი, თუ ის პლაგინის დირექტორიაშია
background: ''
# უნდა განსაზღვროს თუ არა Sonar-მა ავტომატურად ფონის ფერი?
# ეს კეთდება იმისთვის, რომ შეიქმნას კონტრასტი ფონის გამოსახულებასა და ტექსტს შორის
# თუ ეს გამორთულია, Sonar-ი გამოიყენებს შემთხვევით გრადიენტს ფერებისთვის
auto-color: false
# რამდენი პასუხი უნდა მოამზადოს Sonar-მა?
# ეს ხდება ასინქრონულად უკანა ფონზე
precompute: 500
# რამდენ ხანს უნდა დაელოდოს Sonar-ი, სანამ მოთამაშე ვერ გააკეთებს წარმატებულად CAPTCHA-ს?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
max-duration: 45000
max-duration: 30000
# რამდენჯერ ვერ უდნა გაიაროს CAPTCHA მოთამაშემ სანამ ვერიფიკაციას ვერ გაივლის?
max-tries: 3
# სიმბოლოები (ასოები და რიცხვები), რომლებიც დაშვებულია გამოჩნდეს CAPTCHA-ს პასუხში
# It is not recommended to add numbers or letters that might look like one another
# არ არის რეკომენდებული რიცხვების ან ასოების დამატება, რომლებიც შეიძლება ერთმანეთს ჰგავდეს
dictionary: '1235689'
# Which fonts should Sonar use for the map CAPTCHA codes?
font-names:
- Dialog
- DialogInput
- SansSerif
- Monospaced
#ამოწმებს, აგზავნიან თუ არა მოთამაშეები სერვერზე რეალურ კლიენტის სახელს
# ამოწმებს, აგზავნიან თუ არა მოთამაშეები სერვერზე რეალურ კლიენტის სახელს
client-brand:
# უნდა შეამოწმოს თუ არა Sonar-მა ნამდვილია თუ არა მოთამაშის კლიენტის სახელი? (რეკომენდებულია)
enabled: true
@@ -157,6 +169,23 @@ verification:
# მაქსიმალური რაოდენობა ლოგინ პაკეტების, რომელიც მოთამაშემ უნდა გააგზავნოს სანამ გაიკიკება სერვერიდან
max-login-packets: 256
# თუ ჩართულია, მოთამაშე გადავა საწყისი სერვერში ერიფიკაციის წარმატებით გავლის შემდეგ
# ეს ფუნქცია დაემატა Mojang-ის მიერ Minecraft-ის 1.20.5 ვერსიაში
transfer:
# უნდა გადაიყვანოს თუ არა Sonar-მა მოთამაშე საწყის სერვერზე (მათი გაკიკვის ნაცვლად)?
# ამისთვის, თქვენ უნდა ჩართოთ ეს ფუნქცია თქვენი სერვერის კონფიგურაციაში
# დამატებით, შეიძლება გინდოდეთ შეამციროთ შესვლის rate-limiting ი
# რომელსაც ახორციელებს Velocity ან სხვა პროქსები/პლაგინები, რადგან ეს შეიძლება ხელს უშლიდეს
# მოთამაშის სწორად გადაყვანას
enabled: false
# რომელ სერვერზე უნდა გადაიყვანოს Sonar-მა მოთამაშე ვერიფიკაციის გავლის შემდეგ?
# გთხოვთ, შეიყვანოთ სერვერის IP მისამართი, რომლითაც მოთამაშეები ჩვეულებრივ უერთდებიან თქვენს სერვერს
# მაგალითად, შეგიძლიათ ჩაწეროთ "mc.hypixel.net" ან პირდაპირი IP როგორიცაა "1.1.1.1"
destination-host: "play.my-server.com"
# რომელი პორტი უნდა გამოიყენოს Sonar-მა მოთამაშის საწყის სერვერზე გადაყვანისას?
# თუ თქვენს სერვერს არ სჭირდება პორტი შესასვლელად, შეგიძლიათ დატოვოთ ეს 25565-ზე
destination-port: 25565
# ჩაიწეროს თუ არა Sonar-მა ახალი ვერიფიკაციის მცდელობები?
log-connections: true
# ჩაწეროს თუ არა Sonar-მა ახალი ვერიფიკაციის მცდელობები შეტევის დროს?
@@ -164,13 +193,17 @@ verification:
# ჩაიწეროს თუ არა Sonar-მა ყოველი მოძრაობა/პოზიცია ვერიფიკაციის დროს?
# ეს არ არის რეკომენდირებული აქტიურ სერვერებისთვის, მაგრამ შეიძლება გამოსადეგი იყოს ცრუ პოსიტივების გამოსასწორებლად.
debug-xyz-positions: false
# უნდა შეამოწმოს თუ არა Sonar-მა Geyser (Bedrock) მოთამაშეები?
# ეს ფუნქცია ექსპერიმენტულია და შეიძლება გამოიწვიოს პრობლემები
# თუ ეს გამორთულია, Bedrock მოთამაშეები გამოვლენილი იქნება
check-geyser-players: true
# დროის ოდენობა, რომელიც უნდა გავიდეს, სანამ მოთამაშე ტაიმაუტდება სერვერიდან.
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
read-timeout: 3500
read-timeout: 8000
# რამდენ ხანს უნდა დაელოდოს მოთამაშე ვერიფიკაციის დროს ხელახლა დაკავშირებამდე?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
rejoin-delay: 8000
# How long should Sonar remember the amount of failed verifications for a player?
# რამდენ ხანს უნდა ახსოვდეს Sonar-ს მოთამაშე რამდენჯერ იყო წარუმატებელი ვერიფიკაციის დროს?
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
remember-time: 120000
# რამდენ ხნით უნდა აეკრძალოს IP მისამართს შესვლა, როდესაც ვერიფიკაციას ძალიან ხშირად ვერ აკეთებს?
@@ -0,0 +1,222 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
# Algemene instellingen voor Sonar
general:
# Moet Sonar de IP-adressen van spelers in de console registreren?
log-player-addresses: true
# Maximaal aantal spelers online met hetzelfde IP-adres
max-online-per-ip: 3
# Gevoeligheidsinstellingen voor de aanvalstracker
# Bewerk dit alleen als je weet wat je doet
attack-tracker:
# Minimumaantal nieuwe spelers om een aanval te kunnen detecteren
min-players-for-attack: 8
# De hoeveelheid tijd die moet verstrijken voordat een aanval voorbij is
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
min-attack-duration: 30000
# Aantal keren dat een incident moet worden gemeld om als aanval te worden erkend
# Dit nummer fungeert als buffer om valse aanvalsmeldingen eruit te filteren
min-attack-threshold: 2
# De hoeveelheid tijd die moet verstrijken voordat een nieuwe aanval wordt gedetecteerd
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
attack-cooldown-delay: 3000
# U kunt Sonar verbinden met een database om geverifieerde spelers te behouden, zelfs nadat u uw server opnieuw hebt opgestart
# Let op: IP-adressen worden opgeslagen in platte tekst. U bent verantwoordelijk voor het veilig houden van uw database!
database:
# Type database dat Sonar gebruikt om geverifieerde spelers op te slaan
# Mogelijke typen: NONE, MYSQL, MARIADB
type: NONE
# Adres voor authenticatie met de SQL-database
host: localhost
# Poort voor authenticatie met de SQL-database
port: 3306
# Naam van de SQL-database
name: sonar
# Gebruikersnaam voor authenticatie met de SQL-database
username: ''
# Wachtwoord voor authenticatie met de SQL-database
password: ''
# Hoeveel dagen moet Sonar geverifieerde spelers in de database bewaren?
maximum-age: 5
# Elk nieuw inlogverzoek wordt in de wachtrij geplaatst om spam-join-aanvallen te voorkomen
# De wachtrij wordt elke seconde bijgewerkt (20 ticks)
queue:
# Maximaal aantal gelijktijdige peilingen per seconde
max-polls: 30
# Elke nieuwe speler die voor de eerste keer meedoet, wordt naar gestuurd
# een lichtgewicht limbo-server waar geavanceerde bot-controles worden uitgevoerd
verification:
# Wanneer moet Sonar nieuwe spelers verifiëren?
# Mogelijke typen: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Nieuwe spelers worden altijd gecontroleerd (Aanbevolen)
# - DURING_ATTACK: Nieuwe spelers worden alleen gecontroleerd tijdens een aanval
# - NEVER: Nieuwe spelers worden nooit gecontroleerd
timing: ALWAYS
checks:
# Controleert of de valbeweging van de spelers de zwaartekrachtformule van Minecraft volgt
# Alle voorspelde bewegingen worden vooraf berekend om prestaties te besparen
gravity:
# Moet Sonar controleren op geldige zwaartekracht van de cliënt? (Aanbevolen)
enabled: true
# Moet Sonar controleren of de speler tegen blokken botst? (Aanbevolen)
check-collisions: true
# Moet Sonar de speler een CAPTCHA laten oplossen als hij niet slaagt voor de zwaartekracht controle?
captcha-on-fail: false
# Maximaal aantal ticks dat de speler moet laten vallen om het platform te mogen raken
max-movement-ticks: 8
# De spelmodus van de speler tijdens verificatie
# Mogelijke typen: SURVIVAL, CREATIVE, ADVENTURE
# - SURVIVAL: alle UI-componenten zijn zichtbaar
# - CREATIVE: gezondheid en honger zijn verborgen
# - ADVENTURE: alle UI-componenten zijn zichtbaar
gamemode: ADVENTURE
# Controleert of de klant de juiste pakketten verzendt bij het monteren van een voertuig
vehicle:
# Mogelijke typen: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Nieuwe spelers worden altijd op een voertuig gemonteerd (Aanbevolen)
# - DURING_ATTACK: Nieuwe spelers worden alleen tijdens een aanval op een voertuig gemonteerd
# - NEVER: Nieuwe spelers zullen nooit op een voertuig worden gemonteerd
timing: ALWAYS
# Laat de speler een code van een virtuele kaart in de chat typen
map-captcha:
# Wanneer moet Sonar de speler een CAPTCHA laten oplossen?
# Mogelijke typen: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Nieuwe spelers ontvangen altijd een CAPTCHA
# - DURING_ATTACK: Nieuwe spelers ontvangen alleen een CAPTCHA tijdens een aanval
# - NEVER: Nieuwe spelers zullen nooit een CAPTCHA ontvangen (Aanbevolen)
timing: NEVER
# Opties voor de effecten op de afbeelding die aan de speler wordt getoond
# Je moet dit alleen bewerken als je weet wat je doet
effects:
# Voegt willekeurige lijnen toe aan de afbeelding
scratches: true
# Past een ripple-filter toe (golfeffect)
ripple: true
# Past een bump-filter toe (verscherpte tekst)
bump: true
# Vervormingsfilter
distortion:
# Moet Sonar een vervormingsfilter toepassen?
enabled: true
# Welke vorm moet Sonar gebruiken voor het vervormingsfilter?
# Mogelijke typen: 0, 1, 2, 3
# - 0: Kruisen
# - 1: Lijnen
# - 2: Cirkels
# - 3: Vierkantjes
shape: 2
# Afstand tussen vervormde pixels/objecten
distance: 5
# Dichtheid van de vervormde pixels/objecten
density: 0.1
# Hoeveelheid overvloeiing tussen de kleuren van objecten
mix: 0.15
# Pad naar de achtergrondafbeelding die Sonar gebruikt voor de CAPTCHA
# Als de afbeelding niet wordt gevonden, wordt er een ruis gebruikt als standaardachtergrond
# U kunt eenvoudig de naam van het bestand hier plaatsen als het bestand zich in de plugin map bevindt
background: ''
# Moet Sonar automatisch de achtergrondkleur detecteren?
# Dit wordt gedaan om een contrast te creëren tussen de achtergrondafbeelding en de tekst
# Als dit is uitgeschakeld, gebruikt Sonar een willekeurig verloop voor de kleuren
auto-color: false
# Hoeveel antwoorden moet Sonar voorbereiden?
# Deze taak gebeurt asynchroon op de achtergrond
precompute: 500
# Hoe lang moet Sonar wachten totdat de speler de CAPTCHA niet haalt?
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
max-duration: 30000
# Hoe vaak moet een speler de CAPTCHA niet halen voordat de verificatie mislukt?
max-tries: 3
# Tekens (letters en cijfers) die mogen voorkomen in het antwoord op de CAPTCHA
# Het wordt niet aanbevolen om cijfers of letters toe te voegen die op elkaar lijken
dictionary: '1235689'
# Controleert of de speler een geldig Minecraft-klantmerk naar de server verzendt
client-brand:
# Moet Sonar controleren op geldige Minecraft-klantmerk? (Aanbevolen)
enabled: true
# Regex voor het valideren van Minecraft-klantmerken tijdens verificatie
valid-regex: ^[!-~ ]+$
# Maximale lengte van het Minecraft-klantmerk tijdens verificatie
max-length: 64
# Regex voor het valideren van gebruikersnamen tijdens verificatie
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex voor het valideren van de landinstelling van de client tijdens verificatie
valid-locale-regex: ^[a-zA-Z_]+$
# Ping moet een speler hebben om een time-out te krijgen
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
max-ping: 10000
# Maximaal aantal loginpakketten dat de speler moet verzenden om te worden geschopt
max-login-packets: 256
# Indien ingeschakeld, wordt de speler teruggestuurd naar de oorspronkelijke server
# nadat u de bot-verificatie met succes heeft doorstaan.
# Deze functie is geïntroduceerd door Mojang in Minecraft versie 1.20.5
transfer:
# Moet Sonar de speler overbrengen naar de oorspronkelijke server (in plaats van hem te schoppen)?
# Om dit te laten werken, moet u de functie inschakelen in de configuratie van uw server
# Bovendien wilt u mogelijk de beperking van de inlegsnelheid verminderen
# uitgevoerd door Velocity van andere proxy's/plugins, omdat dit de
# speler wordt niet correct weergegeven.
enabled: false
# Naar welke server moet Sonar de speler overbrengen wanneer de verificatie is geslaagd?
# Voer het server-IP in dat door spelers wordt gebruikt om normaal verbinding te maken met uw server.
# U kunt bijvoorbeeld "mc.hypixel.net" of een direct IP-adres zoals "1.1.1.1" invoeren
destination-host: "play.my-server.com"
# Welke poort moet Sonar gebruiken bij het overbrengen van de speler naar de oorspronkelijke server?
# Als uw server geen poort nodig heeft om verbinding mee te maken, kunt u dit op 25565 laten staan.
destination-port: 25565
# Moet Sonar nieuwe verificatiepogingen registreren?
log-connections: true
# Moet Sonar nieuwe verificatiepogingen registreren tijdens aanvallen?
log-during-attack: false
# Moet Sonar elke beweging/positieverandering tijdens de verificatie registreren?
# Dit wordt niet aanbevolen voor productieservers, maar kan nuttig zijn bij het opsporen van fouten.
debug-xyz-positions: false
# Moet Sonar ook Geyser (Bedrock) spelers controleren?
# Deze functie is experimenteel en kan problemen veroorzaken
# Als dit is uitgeschakeld, worden Bedrock-spelers overgeslagen
check-geyser-players: true
# De hoeveelheid tijd die moet verstrijken voordat een speler een time-out krijgt
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
read-timeout: 8000
# Hoe lang moet een speler wachten voordat hij tijdens de verificatie opnieuw verbinding maakt?
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
rejoin-delay: 8000
# Hoe lang moet Sonar het aantal mislukte verificaties voor een speler onthouden?
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
remember-time: 120000
# Hoe lang moet een IP-adres worden ontzegd om in te loggen als de verificatie te vaak mislukt?
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
blacklist-time: 600000
# Hoe vaak moet een speler de verificatie niet doorstaan om op de zwarte lijst te worden geplaatst?
# (Stel deze waarde in op 0 om de zwarte lijst volledig uit te schakelen)
blacklist-threshold: 2
# Lijst met protocol-ID's die niet door Sonar worden gecontroleerd (verificatie-bypass)
# De volledige lijst met alle protocol-ID's vindt u hier:
# https://wiki.vg/Protocol_version_numbers
# Minecraft 1.20 heeft bijvoorbeeld de ID 763.
whitelisted-protocols: []
# Lijst met protocol-ID's die helemaal geen verbinding kunnen maken met de server
blacklisted-protocols: []
@@ -55,9 +55,9 @@ database:
maximum-age: 5
# Каждый новый запрос на вход на сервер будет ставиться в очередь, чтобы избежать спам-атак.
# Очередь обновляется каждые 500 миллисекунд (10 тиков)
# Очередь обновляется каждые 1 секунд (20 тиков)
queue:
# Максимальное количество одновременных опросов очереди за 500 миллисекунд
# Максимальное количество одновременных опросов очереди за 1 секунд
max-polls: 30
# Каждый новый игрок, впервые присоединившийся на сервер, будет отправлен на
@@ -88,12 +88,12 @@ verification:
# - ADVENTURE: все компоненты пользовательского интерфейса видны
gamemode: ADVENTURE
# Checks if the client sends proper packets when mounting a vehicle
# Проверяет, отправляет ли клиент правильные пакеты при садке на транспортное средство
vehicle:
# Possible types: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: New players will always be mounted on a vehicle
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
# - NEVER: New players will never be mounted on a vehicle (Recommended)
# Возможные типы: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: Новые игроки всегда будут садиться на транспортное средство (Рекомендуется)
# - DURING_ATTACK: Новые игроки будут садиться на транспортное средство только во время атаки
# - NEVER: Новые игроки никогда не будут садиться на транспортное средство
timing: ALWAYS
# Заставить игрока ввести код с картинки в чат
@@ -105,38 +105,49 @@ verification:
# - NEVER: Новые игроки никогда не получат капчу (Рекомендуется)
timing: NEVER
# Кастомизация эффектов на картинке, которые показываются игроку
# Рекомендуется редактировать только если вы знаете, что делаете
effects:
# Добавляет свечение к изображению
flare: true
# Добавляет случайные линии к изображению
# Добавляет случайные линии на изображение
scratches: true
# Применяет искажение ряби (синусоидальное)
# Применяет эффект волны (ripple)
ripple: true
# Применяет горизонтальное искажение
smear: true
# Добавляет немного размытости/искажения к изображению
pinch: true
# Применяет цветокоррекцию (насыщенность цвета)
saturation: 0.15
# Применяет горизонтальное треугольное искажение
distortion: 2
# Применяет фильтр bump (увеличивает резкость текста)
bump: true
# Фильтр искажения
distortion:
# Следует ли применять фильтр искажения?
enabled: true
# Какую форму следует использовать Sonar для фильтра искажения?
# Возможные типы: 0, 1, 2, 3
# - 0: Кресты
# - 1: Линии
# - 2: Круги
# - 3: Квадраты
shape: 2
# Расстояние между искаженными пикселями/объектами
distance: 5
# Плотность искаженных пикселей/объектов
density: 0.1
# Степень смешивания между цветами объектов
mix: 0.15
# Путь к фоновому изображению, которое использует Sonar для CAPTCHA
# Если изображение не найдено, будет использован шум как фон по умолчанию
background: ''
# Следует ли Sonar автоматически определять цвет фона?
# Это делается для создания контраста между фоновым изображением и текстом
# Просто укажите имя файла здесь, если файл находится в каталоге плагина
auto-color: false
# Сколько капч должен подготовить Sonar?
# Эта задача выполняется асинхронно в фоновом режиме
precompute: 500
# Как долго Sonar должен ждать, пока игрок не пройдет капча?
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
max-duration: 45000
max-duration: 30000
# Сколько раз игрок должен завалить капчу, чтобы не пройти проверку?
max-tries: 3
# Символы (буквы и цифры), которые разрешено использовать в ответе на капчу
# It is not recommended to add numbers or letters that might look like one another
# Не рекомендуется добавлять цифры или буквы, которые могут быть похожи друг на друга
dictionary: '1235689'
# Какие типы шрифтов следует использовать в Sonar для текста в капче?
font-names:
- Dialog
- DialogInput
- SansSerif
- Monospaced
# Проверяет, отправляет ли игрок на сервер действительный клиентский бренд.
client-brand:
@@ -157,6 +168,24 @@ verification:
# Максимальное количество пакетов для входа на сервер, которое игрок должен отправить, чтобы быть выгнанным
max-login-packets: 256
# Если включено, игрок будет возвращен на исходный сервер
# после успешного прохождения проверки на бота.
# Эта функция была введена Mojang в версии Minecraft 1.20.5
transfer:
# Должен ли Sonar переместить игрока на исходный сервер (вместо выкидывания его)?
# Для этого необходимо включить функцию в конфигурации вашего сервера
# Кроме того, вы можете пожелать снизить количество ограничений на скорость входа
# выполняемых Velocity или другими прокси/плагинами, так как это может помешать
# корректному перемещению игрока.
enabled: false
# На какой сервер должен Sonar переместить игрока после прохождения проверки?
# Введите IP-адрес сервера, который обычно используется игроками для подключения к вашему серверу.
# Например, вы можете указать "mc.hypixel.net" или прямой IP-адрес, например "1.1.1.1"
destination-host: "play.my-server.com"
# Какой порт должен использовать Sonar при перемещении игрока на исходный сервер?
# Если ваш сервер не требует порта для подключения, вы можете оставить это как 25565.
destination-port: 25565
# Должен ли Sonar регистрировать новые попытки проверки?
log-connections: true
# Должен ли Sonar регистрировать новые попытки проверки во время атак?
@@ -164,9 +193,13 @@ verification:
# Должен ли Sonar регистрировать каждое движение/изменение положения во время проверки?
# Это не рекомендуется для рабочих серверов, но может быть полезно для выявления ошибок.
debug-xyz-positions: false
# Следует ли Sonar также проверять игроков Geyser (Bedrock)?
# Эта функция экспериментальна и может вызывать проблемы
# Если это отключено, игроки Bedrock будут пропущены
check-geyser-players: true
# Количество времени, которое должно пройти, прежде чем игрока кикнет.
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
read-timeout: 3500
read-timeout: 8000
# Сколько времени игрок должен ждать перед повторным подключением во время проверки
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
rejoin-delay: 8000
@@ -0,0 +1,212 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# 有问题或需要帮助? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
# 未归类的设置
general:
# Sonar 应该在控制台中打印玩家的 IP 地址吗?
log-player-addresses: true
# 具有相同 IP 地址的在线玩家的最大数量
max-online-per-ip: 3
# 攻击检测器的敏感度设置
# 只有当您知道自己在做什么的时候才编辑它
attack-tracker:
# 在一段时间内至少需要 x 个新玩家加入才满足触发条件.
min-players-for-attack: 8
# 至少经过多少毫秒后才可以解除遭受攻击模式
# (该值为毫秒. 1秒等于1000毫秒.)
min-attack-duration: 30000
# 攻击检测器要重复检测到多少次攻击 才能确认服务器正在遭受攻击?
# 该值作为缓冲区 以避免可能的误判
min-attack-threshold: 2
# 当从遭受攻击标记为解除攻击时 应该需要等待多久才能再次被标记为正在遭受攻击?
# (该值为毫秒. 1秒等于1000毫秒.)
attack-cooldown-delay: 3000
# 您可以让 Sonar 连接到数据库 以便从数据库中保存和读取已经过验证的 IP 和玩家.
# 提示: IP 地址将以纯文本形式保存 请确保您的数据库安全!
database:
# 要连接到的数据库类型
# 可用类型: NONE (不使用数据库), MYSQL, MARIADB
type: NONE
# 数据库的地址
host: localhost
# 数据库的端口
port: 3306
# 数据库名称
name: sonar
# 用于登录数据库的用户名
username: ''
# 用于登录数据库的密码
password: ''
# Sonar 应该在数据库中保留在白名单中的玩家最多多少天?
maximum-age: 5
# 在遭到攻击时 每个新玩家的登录请求将排队 以避免大量连接同时进行验证
# 队列将每1秒刷新一次
queue:
# 每秒最多处理多少个登录请求?
max-polls: 30
# 每个需要验证的玩家都将被发送到一个轻量级的虚拟服务器来检查高级机器人
verification:
# Sonar 应该在什么时候验证玩家?
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: 玩家始终需要验证 直到他们通过验证为止
# - DURING_ATTACK: 玩家仅在服务器疑似遭到攻击时验证
# - NEVER: 玩家不会被验证
timing: ALWAYS
checks:
# 检查玩家的下落移动是否遵循原版客户端.
gravity:
# Sonar 是否应该检查玩家的下落移动? (推荐启用)
enabled: true
# Sonar 是否应该生成方块平台以检查玩家下落与方块之间的碰撞? (推荐启用)
check-collisions: true
# 当玩家验证失败时 是否向他们发送验证码?
captcha-on-fail: false
# 玩家跌落在平台上多少tick后才能被算作站在平台上?
max-movement-ticks: 8
# 玩家在虚拟服务器中的游戏模式
# 可用类型: SURVIVAL, CREATIVE, ADVENTURE
# - SURVIVAL: 生存模式, 所有UI可见
# - CREATIVE: 创造模式, 隐藏血量和饱食度
# - ADVENTURE: 冒险模式, 所有UI可见
gamemode: ADVENTURE
# 检查客户端坐在实体上时是否发送了正确的数据包
vehicle:
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: 始终应用该检查
# - DURING_ATTACK: 仅在疑似遭受攻击时应用该检查
# - NEVER: 永远不应用该检查
timing: ALWAYS
# 地图验证码
map-captcha:
# Sonar 应该在什么时候向玩家发送验证码?
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
# - ALWAYS: 始终应用该检查
# - DURING_ATTACK: 仅在疑似遭受攻击时应用该检查
# - NEVER: 永远不应用该检查
timing: NEVER
# 图像效果选项 (用于混淆自动图像识别)
# You should only edit this if you know what you're doing
effects:
# Adds random lines to the image
scratches: true
# Applies a ripple filter (wavy effect)
ripple: true
# Applies a bump filter (sharpened text)
bump: true
# Distortion filter
distortion:
# Should Sonar apply a distortion filter?
enabled: true
# What shape should Sonar use for the distortion filter?
# Possible types: 0, 1, 2, 3
# - 0: Crosses
# - 1: Lines
# - 2: Circles
# - 3: Squares
shape: 2
# Distance between distorted pixels/objects
distance: 5
# Density of the distorted pixels/objects
density: 0.1
# Amount of blending between the colors of objects
mix: 0.15
# Path to the background image Sonar uses for the CAPTCHA
# If the image isn't found, a noise will be used as the default background
background: ''
# Should Sonar automatically detect the background color?
# This is done to create a contrast between the background image and the text
# You can simply put the name of the file here if file is in the plugin directory
auto-color: false
# Sonar 应该一次准备多少个验证码?
precompute: 500
# Sonar 应该给玩家多少时间完成验证码? (该值为毫秒. 1秒等于1000毫秒.)
max-duration: 30000
# 玩家最多能输入多少次错误的验证码?
max-tries: 3
# 允许出现在验证码答案中的字符
# 不建议添加可能看起来彼此相似的数字或字母
dictionary: '1235689'
# 检查玩家是否正在向服务器发送有效的客户端标识符
client-brand:
# 是否启用此检查? (建议启用)
enabled: true
# 用于检查客户端标识符的正则表达式
valid-regex: ^[!-~ ]+$
# 客户端标识符的最长长度限制
max-length: 64
# 用于检查用户名是否有效的正则表达式
valid-name-regex: ^[a-zA-Z0-9_]+$
# 用于检查客户端语言(例如 zh_CN)的正则表达式
valid-locale-regex: ^[a-zA-Z_]+$
# 玩家必须在多少毫秒内回复Ping?
max-ping: 10000
# 玩家最多能发送多少个数据包用于登录?
max-login-packets: 256
# 开启后 玩家将在验证成功后被重定向到指定的服务器
# 该功能由 Mojang 在 Minecraft 1.20.5 版本中引入
transfer:
# Sonar 是否应该将玩家重定向到指定的服务器而不是踢出玩家?
# 为此您必须在服务器软件的配置文件中允许接受来自transfer的连接.
# 此外 您可能希望减少 Velocity 或其它服务器软件/插件的登录速率限制
# 因为这可能会阻止玩家快速地重新连接到服务器
enabled: false
# 当玩家通过验证后, Sonar 应该将玩家转移到哪个服务器
# 请输入玩家正常连接服务器时使用的服务器IP或域名
# 例如使用域名("mc.hypixel.net") 或使用 IP 地址("1.1.1.1").
destination-host: "play.my-server.com"
# Sonar 应该告诉玩家通过以上域名的什么端口来连接到服务器?
# 如果服务器地址不包含端口 请保留25565.
destination-port: 25565
# 当 Sonar 尝试验证玩家时 是否在控制台中打印它?
log-connections: true
# Sonar 是否应该记录在攻击期间内的验证会话?
log-during-attack: false
# Sonar 是否应该记录验证过程中玩家的移动位置的变化?
# 有助于发现错误 但不建议在非调试环境下的服务器启用该选项.
debug-xyz-positions: false
# Sonar 是否应该检查来自 Geyser 的(基岩版)玩家?
# 此功能是实验性的. 请报告任何因为误判而导致无法通过检查的问题
# 如果关闭该选项 Sonar 将不检查这些玩家以便直接连接到服务器.
check-geyser-players: true
# 要在客户端未响应多少毫秒后因超时而被踢出服务器?
# (该值为毫秒. 1秒等于1000毫秒.)
read-timeout: 8000
# 当玩家在需要验证时(即还未通过检查的情况下)需要等待多少毫秒才可以重新连接?
# (该值为毫秒. 1秒等于1000毫秒.)
rejoin-delay: 8000
# 当玩家验证失败后, Sonar 应该记住他多久?
# (该值为毫秒. 1秒等于1000毫秒.)
remember-time: 120000
# 黑名单应在多少秒后过期? (可能由于频繁验证失败. 在黑名单中将会被拒绝进入服务器.)
# (该值为毫秒. 1秒等于1000毫秒.)
blacklist-time: 600000
# 玩家必须验证失败多少次才会被列入黑名单? (设置为0以完全禁用)
blacklist-threshold: 2
# Sonar 不应该检查哪些协议? (即 跳过检查)
# 您可以在 https://wiki.vg/Protocol_version_numbers 找到所有协议号
# 例如 Minecraft 1.20 对应的协议号为763.
whitelisted-protocols: []
# 黑名单中的协议将阻止所有使用该协议的客户端.
blacklisted-protocols: []
@@ -184,9 +184,6 @@ verification:
captcha:
# Nachricht, die dem Spieler angezeigt wird, wenn er den CAPTCHA-Code im Chat eingeben muss
enter-code: '%prefix%<green>Bitte gib den Code im Chat ein, der auf der Karte angezeigt wird.'
# Timer, der dem Spieler angezeigt wird, wenn er den CAPTCHA-Code im Chat eingeben muss
# (Setze dies auf '' um die Aktionsleisten-Nachricht zu deaktivieren)
action-bar: '%prefix%<green>Du hast %time-left% Sekunden Zeit, um den Code im Chat einzugeben'
# Nachricht, die dem Spieler angezeigt wird, wenn er den falschen Code im Chat eingibt
incorrect: '%prefix%<red>Du hast den falschen Code eingegeben. Bitte versuche es erneut.'
@@ -24,7 +24,7 @@ header:
# Placeholder for every '%footer%' in this configuration file
footer:
- <gray>If you believe that this is an error, contact an administrator.
# Disconnect message that is shown when someone joins but there are too many online players with their IP address
# Disconnect message that is shown when someone joins, but there are too many online players with their IP address
too-many-online-per-ip:
- '%header%'
- <red>There are too many players online with your IP address.
@@ -32,21 +32,21 @@ too-many-online-per-ip:
# Translations for commands
commands:
# Message that is shown when a player tries running /sonar without permission
# Message shown when a player tries running /sonar without permission
no-permission: '%prefix%<red>You do not have permission to execute this command.'
# Message that is shown when someone uses a command incorrectly
# Message shown when someone uses a command incorrectly
incorrect-usage: '%prefix%<red>Usage: /sonar %usage%'
# Message that is shown when someone provides an invalid IP address (Invalid format)
# Message shown when someone provides an invalid IP address (Invalid format)
invalid-ip-address: '%prefix%The IP address you provided seems to be invalid.'
# Message that is shown when the console runs a command that is player-only
# Message shown when the console runs a command that is player-only
player-only: '%prefix%<red>You can only execute this command as a player.'
# Message that is shown when a player runs a command that is console-only
# Message shown when a player runs a command that is console-only
console-only: '%prefix%<red>For security reasons, you can only execute this command
through console.'
# Message that is shown when a player executes Sonar commands too quickly
# Message shown when a player executes Sonar commands too quickly
cool-down: '%prefix%<red>You can only execute this command every 0.5 seconds.'
cool-down-left: '%prefix%<red>Please wait another <bold>%time-left%s<reset><red>.'
# Message that is shown when a player does not have permission to execute a certain subcommand
# Message shown when a player does not have permission to execute a certain subcommand
subcommand-no-permission: '%prefix%<red>You do not have permission to execute this
subcommand. <gray>(%permission%)'
@@ -71,73 +71,73 @@ commands:
# Translations for '/sonar reload'
reload:
# Message that is shown when someone starts reloading Sonar
# Message shown when someone starts reloading Sonar
start: '%prefix%Reloading Sonar...'
# Message that is shown when Sonar has finished reloading
# Message shown when Sonar has finished reloading
finish: '%prefix%<green>Successfully reloaded <gray>(%taken%ms)'
# Translations for '/sonar verbose'
verbose:
# Message that is shown when a player subscribes to Sonar verbose
# Message shown when a player subscribes to Sonar verbose
subscribed: '%prefix%You are now viewing Sonar verbose.'
# Message that is shown when a player unsubscribes from Sonar verbose
# Message shown when a player unsubscribes from Sonar verbose
unsubscribed: '%prefix%You are no longer viewing Sonar verbose.'
# Translations for '/sonar notify'
notify:
# Message that is shown when a player subscribes to Sonar attack notifications
# Message shown when a player subscribes to Sonar attack notifications
subscribed: '%prefix%You are now viewing Sonar attack notifications.'
# Message that is shown when a player unsubscribes from Sonar attack notifications
# Message shown when a player unsubscribes from Sonar attack notifications
unsubscribed: '%prefix%You are no longer viewing Sonar attack notifications.'
# Translations for '/sonar blacklist'
blacklist:
# Message that is shown when someone tries clearing the blacklist but is is empty
# Message shown when someone tries clearing the blacklist but is empty
empty: '%prefix%The blacklist is currently empty. Therefore, no IP addresses were
removed from the blacklist.'
# Message that is shown when someone clears the blacklist
# Message shown when someone clears the blacklist
cleared: '%prefix%You successfully removed a total of %removed% IP address(es)
from the blacklist.'
# Message that is shown when someone checks the size of the blacklist
# Message shown when someone checks the size of the blacklist
size: '%prefix%The blacklist currently contains %amount% IP address(es).'
# Message that is shown when someone adds an IP address to the blacklist
# Message shown when someone adds an IP address to the blacklist
added: '%prefix%Successfully added %ip% to the blacklist.'
# Message that is shown when someone adds an IP address to the blacklist that is verified
# Message shown when someone adds an IP address to the blacklist that is verified
added-warning: '%prefix%<red>Warning: <white>%ip% is currently whitelisted. Consider
removing the IP address from the list of verified players to avoid potential
issues.'
# Message that is shown when someone removes an IP address from the blacklist
# Message shown when someone removes an IP address from the blacklist
removed: '%prefix%Successfully removed %ip% from the blacklist.'
# Message that is shown when someone adds an IP address to the blacklist, but it is already blacklisted
# Message shown when someone adds an IP address to the blacklist, but it is already blacklisted
duplicate-ip: '%prefix%The IP address you provided is already blacklisted.'
# Message that is shown when someone removes an IP address from the blacklist, but it is not blacklisted
# Message shown when someone removes an IP address from the blacklist, but it is not blacklisted
ip-not-found: '%prefix%The IP address you provided is not blacklisted.'
# Translations for '/sonar verified'
verified:
# Message that is shown when someone tries clearing the list of verified players but is empty
# Message shown when someone tries clearing the list of verified players but is empty
empty: '%prefix%The list of verified players is currently empty. Therefore, no
players were unverified.'
# Message that is shown when someone clears the list of verified players
# Message shown when someone clears the list of verified players
cleared: '%prefix%You successfully unverified a total of %removed% unique player(s).'
# Message that is shown when someone checks the size of the list of verified players
# Message shown when someone checks the size of the list of verified players
size: '%prefix%There are currently %amount% unique player(s) verified.'
# Message that is shown when someone un-verifies an IP address
# Message shown when someone un-verifies an IP address
removed: '%prefix%Successfully unverified %ip%.'
# Message that is shown when someone verifies an IP address
# Message shown when someone verifies an IP address
added: '%prefix%Successfully verified %ip%.'
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
# Message shown when someone tries to verify an IP address/UUID that are already verified
already: '%prefix%The IP address and UUID you provided are already verified.'
# Message that is shown when someone un-verifies an IP address, but it is not verified
# Message shown when someone un-verifies an IP address, but it is not verified
ip-not-found: '%prefix%The IP address you provided is not verified.'
# Message that is shown when someone tries un-verifying the same IP address twice (double operation)
# Message shown when someone tries un-verifying the same IP address twice (double operation)
blocked: '%prefix%Please wait for the current operation to finish.'
# Translations for '/sonar statistics'
statistics:
# Informational message that is shown above everything when viewing the statistics
header: '%prefix%Showing %type% statistics for this session:'
# Message that is shown when a player tries viewing an unknown statistic
# Message shown when a player tries viewing an unknown statistic
unknown-type: '%prefix%<red>Unknown statistics type! Available statistics: <gray>%statistics%'
# Format of the general statistics message
general:
@@ -174,25 +174,21 @@ commands:
verification:
# Translations for all debug messages during the verification
logs:
# Message that is logged to console whenever a new player joins the server
# Message logged to console whenever a new player joins the server
connection: '%name%%ip% (%protocol%) has connected.'
# Message that is logged to console whenever a player fails verification
# Message logged to console whenever a player fails verification
failed: '%ip% (%protocol%) has failed the bot check for: %reason%'
# Message that is logged to console whenever a player is blacklisted
# Message logged to console whenever a player is blacklisted
blacklisted: '%ip% (%protocol%) was blacklisted for too many failed attempts'
# Message that is logged to console whenever a player is verified
# Message logged to console whenever a player is verified
successful: '%name% has been verified successfully (%time%s!).'
# Messages that are shown to the player when they are supposed to enter the CAPTCHA
captcha:
# Message that is shown to the player when they have to enter the answer to the captcha
# Message shown to the player when they have to enter the answer to the captcha
enter-code: '%prefix%<green>Please enter the code in chat that is displayed on
the map.'
# Timer that is shown to the player when they have to enter the answer to the captcha
# (Set this to '' to disable the action bar message)
action-bar: '%prefix%<green>You have %time-left% seconds left to enter the code
in chat'
# Message that is shown to the player when they enter the wrong answer in chat
# Message shown to the player when they enter the wrong answer in chat
incorrect: '%prefix%<red>You have entered the wrong code. Please try again.'
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
@@ -0,0 +1,300 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
#
# Espace réservé pour tout '%prefix%' dans ce fichier de configuration
prefix: <b><yellow>Sonar</b> <gray>» <white>
# Espace réservé pour tout '%support-url%' dans ce fichier de configuration
support-url: https://jonesdev.xyz/discord/
# Espace réservé pour tout '%header%' dans ce fichier de configuration
header:
- <b><yellow>Sonar</b><reset>
- ''
# Espace réservé pour tout '%footer%' dans ce fichier de configuration
footer:
- <gray>Si vous pensez que c'est une erreur, veuillez contacter un administrateur.
# Message de déconnexion affiché lorsqu'un joueur se connecte mais qu'il y a trop de joueurs en ligne avec leur adresse IP
too-many-online-per-ip:
- '%header%'
- <red>Il y a trop de joueurs en ligne avec votre adresse IP.
- '%footer%'
# Traductions pour les commandes
commands:
# Message affiché lorsqu'un joueur tente d'exécuter /sonar sans permission
no-permission: "%prefix%<red>Vous n'avez pas la permission d'exécuter cette commande."
# Message affiché lorsqu'une commande est utilisée de manière incorrecte
incorrect-usage: '%prefix%<red>Utilisation : /sonar %usage%'
# Message affiché lorsqu'une adresse IP invalide est fournie (format invalide)
invalid-ip-address: "%prefix%L'adresse IP que vous avez fournie semble invalide."
# Message affiché lorsque la console exécute une commande qui est uniquement pour les joueurs
player-only: "%prefix%<red>Vous ne pouvez exécuter cette commande qu'en tant que joueur."
# Message affiché lorsqu'un joueur exécute une commande qui est uniquement pour la console
console-only: '%prefix%<red>Pour des raisons de sécurité, vous ne pouvez exécuter cette commande que via la console.'
# Message affiché lorsqu'un joueur exécute trop rapidement les commandes Sonar
cool-down: "%prefix%<red>Vous ne pouvez exécuter cette commande qu'une fois toutes les 0,5 secondes."
cool-down-left: '%prefix%<red>Veuillez attendre encore <bold>%time-left%<reset><red>.'
# Message affiché lorsqu'un joueur n'a pas la permission d'exécuter une certaine sous-commande
subcommand-no-permission: "%prefix%<red>Vous n'avez pas la permission d'exécuter cette sous-commande. <gray>(%permission%)"
# Traductions pour '/sonar'
main:
# Message informatif affiché au-dessus de tout lors de l'exécution de la commande principale
header:
- <yellow>Exécution de <b>Sonar</b> %version% sur %platform%.
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
Jones Development et les contributeurs de Sonar</click>
- ''
- <gray>Besoin d'aide ou avez-vous des questions ?
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Cliquez
pour ouvrir Discord)'>Ouvrez un ticket sur Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Cliquez
pour ouvrir GitHub)'>ou ouvrez un nouveau problème sur GitHub.
- ''
# Format de la liste des sous-commandes affichée lors de l'exécution de la commande principale
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Seulement pour les joueurs : </gray>%only_players%<br><gray>Nécessite la console : </gray>%require_console%<br><gray>Permission : </gray><white>%permission%<br><gray>Alias : </gray>%aliases%''><dark_aqua> ▪ <gray>/sonar %subcommand% <white>%description%'
# Traductions pour '/sonar reload'
reload:
# Message affiché lorsque quelqu'un commence à recharger Sonar
start: '%prefix%Rechargement de Sonar...'
# Message affiché lorsque Sonar a fini de recharger
finish: '%prefix%<green>Rechargement réussi <gray>(%taken%ms)'
# Traductions pour '/sonar verbose'
verbose:
# Message affiché lorsqu'un joueur s'abonne aux détails verbeux de Sonar
subscribed: '%prefix%Vous visualisez maintenant les détails verbeux de Sonar.'
# Message affiché lorsqu'un joueur se désabonne des détails verbeux de Sonar
unsubscribed: '%prefix%Vous ne visualisez plus les détails verbeux de Sonar.'
# Traductions pour '/sonar notify'
notify:
# Message affiché lorsqu'un joueur s'abonne aux notifications d'attaque de Sonar
subscribed: "%prefix%Vous visualisez maintenant les notifications d'attaque de Sonar."
# Message affiché lorsqu'un joueur se désabonne des notifications d'attaque de Sonar
unsubscribed: "%prefix%Vous ne visualisez plus les notifications d'attaque de Sonar."
# Traductions pour '/sonar blacklist'
blacklist:
# Message affiché lorsque quelqu'un tente de vider la liste noire mais qu'elle est vide
empty: "%prefix%La liste noire est actuellement vide. Par conséquent, aucune adresse IP n'a été supprimée de la liste noire."
# Message affiché lorsque quelqu'un vide la liste noire
cleared: '%prefix%Vous avez réussi à supprimer un total de %removed% adresse(s) IP de la liste noire.'
# Message affiché lorsque quelqu'un vérifie la taille de la liste noire
size: '%prefix%La liste noire contient actuellement %amount% adresse(s) IP.'
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire
added: '%prefix%Adresse IP %ip% ajoutée avec succès à la liste noire.'
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire qui est vérifiée
added-warning: "%prefix%<red>Avertissement : <white>%ip% est actuellement sur liste blanche. Considérez la suppression de l'adresse IP de la liste des joueurs vérifiés pour éviter des problèmes potentiels."
# Message affiché lorsque quelqu'un supprime une adresse IP de la liste noire
removed: '%prefix%Adresse IP %ip% supprimée avec succès de la liste noire.'
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire, mais qu'elle est déjà en liste noire
duplicate-ip: "%prefix%L'adresse IP que vous avez fournie est déjà sur la liste noire."
# Message affiché lorsque quelqu'un supprime une adresse IP de la liste noire, mais qu'elle n'est pas en liste noire
ip-not-found: "%prefix%L'adresse IP que vous avez fournie ne figure pas sur la liste noire."
# Traductions pour '/sonar verified'
verified:
# Message affiché lorsque quelqu'un tente de vider la liste des joueurs vérifiés mais qu'elle est vide
empty: "%prefix%La liste des joueurs vérifiés est actuellement vide. Par conséquent, aucun joueur n'a été désapprouvé."
# Message affiché lorsque quelqu'un vide la liste des joueurs vérifiés
cleared: '%prefix%Vous avez réussi à désapprouver un total de %removed% joueur(s) unique(s).'
# Message affiché lorsque quelqu'un vérifie la taille de la liste des joueurs vérifiés
size: '%prefix%Il y a actuellement %amount% joueur(s) unique(s) vérifié(s).'
# Message affiché lorsque quelqu'un désapprouve une adresse IP
removed: '%prefix%Adresse IP %ip% désapprouvée avec succès.'
# Message affiché lorsque quelqu'un vérifie une adresse IP
added: '%prefix%Adresse IP %ip% vérifiée avec succès.'
# Message affiché lorsque quelqu'un tente de vérifier une adresse IP/UUID qui est déjà vérifiée
already: "%prefix%L'adresse IP et l'UUID que vous avez fournis sont déjà vérifiés."
# Message affiché lorsque quelqu'un désapprouve une adresse IP, mais qu'elle n'est pas vérifiée
ip-not-found: "%prefix%L'adresse IP que vous avez fournie n'est pas vérifiée."
# Message affiché lorsque quelqu'un tente de désapprouver la même adresse IP deux fois (double opération)
blocked: "%prefix%Veuillez attendre la fin de l'opération en cours."
# Traductions pour '/sonar statistics'
statistics:
# Message informatif affiché au-dessus de tout lors de la visualisation des statistiques
header: '%prefix%Affichage des statistiques %type% pour cette session :'
# Message affiché lorsqu'un joueur tente de visualiser une statistique inconnue
unknown-type: '%prefix%<red>Type de statistiques inconnu ! Statistiques disponibles : <gray>%statistics%'
# Format du message de statistiques général
general:
- ' <dark_aqua>▪ <gray>Adresses IP vérifiées : <white>%verified%'
- ' <dark_aqua>▪ <gray>En train de vérifier les adresses IP : <white>%verifying%'
- ' <dark_aqua>▪ <gray>Adresses IP en liste noire : <white>%blacklisted%'
- ' <dark_aqua>▪ <gray>Connexions en attente actuelles : <white>%queued%'
- ' <dark_aqua>▪ <gray>Total des connexions non uniques : <white>%total_joins%'
- ' <dark_aqua>▪ <gray>Total des tentatives de vérification : <white>%total_attempts%'
- ' <dark_aqua>▪ <gray>Total des vérifications échouées : <white>%total_failed%'
- ' <dark_aqua>▪ <gray>Temps de fonctionnement du serveur : <white>%uptime%'
# Format du message de statistiques CPU
cpu:
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du processus : <white>%process_cpu%%'
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du système : <white>%system_cpu%%'
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du processus : <white>%average_process_cpu%%'
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du système : <white>%average_system_cpu%%'
- ' <dark_aqua>▪ <gray>Charge système générale : <white>%load_average%%'
- ' <dark_aqua>▪ <gray>Nombre total de processeurs virtuels : <white>%virtual_cores%'
# Format du message de statistiques de mémoire
memory:
- ' <dark_aqua>▪ <gray>Mémoire libre totale : <white>%free_memory%'
- ' <dark_aqua>▪ <gray>Mémoire utilisée totale : <white>%used_memory%'
- ' <dark_aqua>▪ <gray>Mémoire maximale totale : <white>%max_memory%'
- ' <dark_aqua>▪ <gray>Mémoire allouée totale : <white>%total_memory%'
# Format du message de statistiques réseau
network:
- ' <dark_aqua>▪ <gray>Bande passante entrante actuelle utilisée : <white>%incoming%'
- ' <dark_aqua>▪ <gray>Bande passante sortante actuelle utilisée : <white>%outgoing%'
- ' <dark_aqua>▪ <gray>Bande passante entrante totale utilisée : <white>%ttl_incoming%'
- ' <dark_aqua>▪ <gray>Bande passante sortante totale utilisée : <white>%ttl_outgoing%'
# Traductions pour tous les messages pendant le processus de vérification
verification:
# Traductions pour tous les messages de débogage pendant la vérification
logs:
# Message enregistré dans la console chaque fois qu'un nouveau joueur rejoint le serveur
connection: "%name%%ip% (%protocol%) s'est connecté."
# Message enregistré dans la console chaque fois qu'un joueur échoue à la vérification
failed: '%ip% (%protocol%) a échoué au test du bot pour : %reason%'
# Message enregistré dans la console chaque fois qu'un joueur est sur liste noire
blacklisted: '%ip% (%protocol%) a été mis sur liste noire pour trop de tentatives échouées'
# Message enregistré dans la console chaque fois qu'un joueur est vérifié
successful: '%name% a été vérifié avec succès (%time%s !).'
# Messages affichés au joueur lorsqu'il doit entrer le CAPTCHA
captcha:
# Message affiché au joueur lorsqu'il doit entrer la réponse au CAPTCHA
enter-code: '%prefix%<green>Veuillez entrer le code affiché sur la carte.'
# Message affiché au joueur lorsqu'il entre la mauvaise réponse dans le chat
incorrect: '%prefix%<red>Vous avez entré le mauvais code. Veuillez réessayer.'
# Message de déconnexion affiché lorsque quelqu'un rejoint alors que le CAPTCHA n'a pas été préparé
currently-preparing:
- '%header%'
- <yellow>Vos données anti-bot n'ont pas encore été préparées.
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un se reconnecte trop rapidement pendant la vérification
too-fast-reconnect:
- '%header%'
- <gold>Vous avez rejoint trop rapidement, réessayez plus tard.
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est déjà en vérification
already-verifying:
- '%header%'
- <red>Votre adresse IP est actuellement en cours de vérification.
- <red>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est déjà en attente de vérification
already-queued:
- '%header%'
- <red>Votre adresse IP est actuellement en attente de vérification.
- <red>Veuillez patienter quelques minutes avant d'essayer de vous vérifier à nouveau.
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est temporairement mis sur liste noire
blacklisted:
- '%header%'
- <red>Vous êtes actuellement interdit d'entrer sur le serveur.
- <red>Veuillez patienter quelques minutes pour pouvoir rejoindre le serveur à nouveau.
- <gold>Erreur ? <gray>%support-url%
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un rejoint avec un nom d'utilisateur non valide
invalid-username:
- '%header%'
- <red>Votre nom d'utilisateur contient des caractères non valides.
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un rejoint avec une version sur liste noire
blacklisted-protocol:
- '%header%'
- <red>Vous utilisez une version non autorisée sur notre serveur.
- <gold>Besoin d'aide pour vous connecter ? <gray>%support-url%
- '%footer%'
# Message de déconnexion affiché lorsque quelqu'un réussit la vérification
success:
- '%header%'
- <green>Vous avez réussi la vérification.
- <white>Vous pouvez maintenant jouer sur le serveur lorsque vous vous reconnectez.
# Message de déconnexion affiché lorsque quelqu'un échoue à la vérification
failed:
- '%header%'
- <red>Vous avez échoué à la vérification du bot.
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
- <gold>Besoin d'aide ? <gray>%support-url%
- '%footer%'
# Translations for Sonar's actionbar verbose messages
verbose:
layout:
# Mise en page pour le verbeux de la barre d'action de Sonar
# Espacements réservés :
# - '%queued%' Nombre de connexions en attente
# - '%verifying%' Nombre de connexions en cours de vérification
# - '%blacklisted%' Nombre d'adresses IP sur liste noire
# - '%total-joins%' Nombre total de connexions tentées
# - '%verify-total%' Nombre total de tentatives de vérification
# - '%verify-success%' Nombre d'adresses IP vérifiées
# - '%verify-failed%' Nombre de vérifications échouées
# - '%logins-per-second%' Nombre de connexions par seconde
# - '%connections-per-second%' Nombre de connexions par seconde
# - '%attack-duration%' Durée de l'attaque actuelle
# - '%incoming-traffic%' Utilisation de la bande passante entrante par seconde
# - '%outgoing-traffic%' Utilisation de la bande passante sortante par seconde
# - '%incoming-traffic-ttl%' Utilisation totale de la bande passante entrante
# - '%outgoing-traffic-ttl%' Utilisation totale de la bande passante sortante
# - '%used-memory%' Quantité de mémoire utilisée par le processus
# - '%total-memory%' Quantité de mémoire totale du processus
# - '%max-memory%' Quantité de mémoire maximale du processus
# - '%free-memory%' Quantité de mémoire libre du processus
# - '%animation%' Symbole animé personnalisable
# Traductions pour la sortie verbeuse normale de Sonar
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
<white>%logins-per-second% <dark_aqua>╺ <gray>Vérification <white>%verifying% <dark_aqua>|
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# Traductions pour la sortie verbeuse lors d'une attaque
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
<white>%logins-per-second% <dark_aqua>╺ <gray>Queue <white>%queued% <dark_aqua>╺
<gray>Vérification <white>%verifying% <dark_aqua>╺ <gray>Durée <white>%attack-duration%
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# Animation pour la sortie verbeuse
# Alternatives:
# - ▙, ▛, ▜, ▟
# - ⬈, ⬊, ⬋, ⬉
animation:
- ◜
- ◝
- ◞
- ◟
# Traductions pour les messages affichés en cas d'attaque
notifications:
# Mise en page du titre de la notification d'attaque de Sonar
title: <yellow><b>Sonar<reset>
# Mise en page du sous-titre de la notification d'attaque de Sonar
subtitle: <gray>A bot attack has been detected
# Mise en page de la notification d'attaque de Sonar dans le chat
chat:
- ''
- <yellow><b>Sonar<reset>
- ''
- <gray>Une attaque de bot a été détectée.
- ' <dark_aqua>▪ <gray>Connexions par seconde: <white>%logins-per-second%'
- ' <dark_aqua>▪ <gray>Vérification des joueurs: <white>%verifying%'
- ' <dark_aqua>▪ <gray>Joueurs en file d''attente: <white>%queued%'
- ''
- <gray>View more information using <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click
to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar
stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
- ''
@@ -0,0 +1,311 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
# Tijdelijke aanduiding voor elke '%prefix%' in dit configuratiebestand
prefix: <b><yellow>Sonar</b> <gray>» <white>
# Tijdelijke aanduiding voor elke '%support-url%' in dit configuratiebestand
support-url: https://jonesdev.xyz/discord/
# Tijdelijke aanduiding voor elke '%header%' in dit configuratiebestand
header:
- <b><yellow>Sonar</b><reset>
- ''
# Tijdelijke aanduiding voor elke '%footer%' in dit configuratiebestand
footer:
- <gray>Als u denkt dat dit een fout is, neem dan contact op met een beheerder.
# Verbindingsbericht dat wordt weergegeven als iemand zich aansluit, maar er zijn te veel online spelers met hun IP-adres
too-many-online-per-ip:
- '%header%'
- <red>Er zijn te veel spelers online met uw IP-adres.
- '%footer%'
# Vertalingen voor commandos
commands:
# Bericht dat wordt weergegeven wanneer een speler zonder toestemming /sonar probeert uit te voeren
no-permission: '%prefix%<red>U heeft geen toestemming om deze opdracht uit te voeren.'
# Bericht dat verschijnt wanneer iemand een commando verkeerd gebruikt
incorrect-usage: '%prefix%<red>Gebruik: /sonar %usage%'
# Bericht dat wordt weergegeven wanneer iemand een ongeldig IP-adres opgeeft (ongeldig formaat)
invalid-ip-address: '%prefix%Het door u opgegeven IP-adres lijkt ongeldig te zijn.'
# Bericht dat wordt weergegeven wanneer de console een opdracht uitvoert die alleen voor spelers is
player-only: '%prefix%<red>Je kunt dit commando alleen als speler uitvoeren.'
# Bericht dat wordt weergegeven wanneer een speler een opdracht uitvoert die alleen voor de console geldt
console-only: '%prefix%<red>Om veiligheidsredenen kunt u deze opdracht alleen uitvoeren
via console.'
# Bericht dat wordt weergegeven wanneer een speler Sonar-opdrachten te snel uitvoert
cool-down: '%prefix%<red>U kunt dit commando slechts elke 0,5 seconde uitvoeren.'
cool-down-left: '%prefix%<red>Wacht nog <bold>%time-left%s<reset><red>.'
# Bericht dat wordt getoond wanneer een speler geen toestemming heeft om een bepaald subcommand uit te voeren
subcommand-no-permission: '%prefix%<red>U heeft geen toestemming om dit uit te voeren
subcommand. <gray>(%permission%)'
# Vertalingen voor '/sonar'
main:
# Informatief bericht dat boven alles wordt weergegeven bij het uitvoeren van de hoofdopdracht
header:
- <yellow><b>Sonar</b> %version% draaien op %platform%.
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
Jones Development and Sonar Contributors</click>
- ''
- <gray>Hulp nodig of vragen?
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Klik
om Discord te openen)'>Open een ticket op de Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Klik
om GitHub te openen)'>of open een nieuw probleem op GitHub.
- ''
# Opmaak van de lijst met subcommand die wordt weergegeven bij het uitvoeren van de hoofdopdracht
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Alleen
spelers: </gray>%only_players%<br><gray>Vereist console: </gray>%require_console%<br><gray>Toestemming:
</gray><white>%permission%<br><gray>Aliassen: </gray>%aliases%''><dark_aqua>
▪ <gray>/sonar %subcommand% <white>%description%'
# Vertalingen voor '/sonar reload'
reload:
# Bericht dat wordt weergegeven wanneer iemand Sonar begint te herladen
start: '%prefix%Sonar opnieuw laden...'
# Bericht dat wordt weergegeven wanneer Sonar klaar is met herladen
finish: '%prefix%<green>Opnieuw geladen <gray>(%taken%ms)'
# Vertalingen voor '/sonar verbose'
verbose:
# Bericht dat wordt weergegeven wanneer een speler zich abonneert op Sonar uitgebreid
subscribed: '%prefix%U bekijkt nu Sonar uitgebreid.'
# Bericht dat wordt weergegeven wanneer een speler zich afmeldt voor Sonar uitgebreid
unsubscribed: '%prefix%Je bekijkt Sonar niet langer uitgebreid.'
# Vertalingen voor '/sonar notify'
notify:
# Bericht dat wordt weergegeven wanneer een speler zich abonneert op Sonar aanval meldingen
subscribed: '%prefix%U bekijkt nu Sonar aanval meldingen.'
# Bericht dat wordt weergegeven wanneer een speler zich afmeldt voor Sonar aanval meldingen
unsubscribed: '%prefix%Je bekijkt Sonar niet langer Sonar aanval meldingen.'
# Vertalingen voor '/sonar blacklist'
blacklist:
# Bericht dat wordt weergegeven wanneer iemand de zwarte lijst probeert te wissen, maar deze leeg is
empty: '%prefix%De zwarte lijst is momenteel leeg. Daarom waren er geen IP-adressen
verwijderd van de zwarte lijst.'
# Bericht dat wordt weergegeven wanneer iemand de zwarte lijst wist
cleared: '%prefix%Je hebt in totaal %removed% IP-adres(sen) met succes verwijderd
van de zwarte lijst.'
# Bericht dat wordt getoond wanneer iemand de omvang van de zwarte lijst controleert
size: '%prefix%De zwarte lijst bevat momenteel %amount% IP-adres(sen).'
# Bericht dat wordt getoond wanneer iemand een IP-adres aan de zwarte lijst toevoegt
added: '%prefix%%ip% is succesvol toegevoegd aan de zwarte lijst.'
# Bericht dat wordt getoond wanneer iemand een IP-adres toevoegt aan de zwarte lijst die is geverifieerd
added-warning: '%prefix%<red>Attentie: <white>%ip% staat momenteel op de witte lijst. Overwegen
het verwijderen van het IP-adres uit de lijst met geverifieerde spelers om potentieel te voorkomen
problemen.'
# Bericht dat verschijnt wanneer iemand een IP-adres van de zwarte lijst verwijdert
removed: '%prefix%%ip% is met succes verwijderd van de zwarte lijst.'
# Bericht dat wordt weergegeven wanneer iemand een IP-adres aan de zwarte lijst toevoegt, maar dit al op de zwarte lijst staat
duplicate-ip: '%prefix%Het door u opgegeven IP-adres staat al op de zwarte lijst.'
# Bericht dat verschijnt wanneer iemand een IP-adres van de zwarte lijst verwijdert, maar dit niet op de zwarte lijst staat
ip-not-found: '%prefix%Het door u opgegeven IP-adres staat niet op de zwarte lijst.'
# Vertalingen voor '/sonar verified'
verified:
# Bericht dat wordt weergegeven wanneer iemand de lijst met geverifieerde spelers probeert te wissen, maar leeg is
empty: '%prefix%De lijst met geverifieerde spelers is momenteel leeg. Daarom nee
spelers waren niet geverifieerd.'
# Bericht dat wordt weergegeven wanneer iemand de lijst met geverifieerde spelers wist
cleared: '%prefix%Je hebt met succes de verificatie van in totaal %removed% unieke speler(s) ongedaan gemaakt.'
# Bericht dat wordt weergegeven wanneer iemand de grootte van de lijst met geverifieerde spelers controleert
size: '%prefix%Er zijn momenteel %amount% unieke speler(s) geverifieerd.'
# Bericht dat wordt weergegeven wanneer iemand de verificatie van een IP-adres ongedaan maakt
removed: '%prefix%De verificatie van %ip% is succesvol ingetrokken.'
# Bericht dat wordt weergegeven wanneer iemand een IP-adres verifieert
added: '%prefix%%ip% succesvol geverifieerd.'
# Bericht dat wordt weergegeven wanneer iemand een IP-adres/UUID probeert te verifiëren die al is geverifieerd
already: '%prefix%Het IP-adres en de UUID die u heeft opgegeven, zijn al geverifieerd.'
# Bericht dat wordt weergegeven wanneer iemand de verificatie van een IP-adres ongedaan maakt, maar het is niet geverifieerd
ip-not-found: '%prefix%Het door u opgegeven IP-adres is niet geverifieerd.'
# Bericht dat wordt weergegeven wanneer iemand tweemaal hetzelfde IP-adres probeert te de-verifiëren (dubbele handeling)
blocked: '%prefix%Wacht tot de huidige bewerking is voltooid.'
# Vertalingen voor '/sonar statistics'
statistics:
# Informatieve boodschap die boven alles wordt getoond bij het bekijken van de statistieken
header: '%prefix%Er worden %type% statistieken voor deze sessie weergegeven:'
# Bericht dat wordt weergegeven wanneer een speler een onbekende statistiek probeert te bekijken
unknown-type: '%prefix%<red>Onbekend statistieke! Beschikbare statistieken: <gray>%statistics%'
# Formaat van het algemene statistische bericht
general:
- ' <dark_aqua>▪ <gray>Geverifieerde IP-adressen: <white>%verified%'
- ' <dark_aqua>▪ <gray>IP-adressen verifiëren: <white>%verifying%'
- ' <dark_aqua>▪ <gray>IP-adressen op de zwarte lijst: <white>%blacklisted%'
- ' <dark_aqua>▪ <gray>Wachtrij staande aanmeldingen: <white>%queued%'
- ' <dark_aqua>▪ <gray>Totaal aantal niet-unieke joins: <white>%total_joins%'
- ' <dark_aqua>▪ <gray>Totaal aantal verificatiepogingen: <white>%total_attempts%'
- ' <dark_aqua>▪ <gray>Totaal aantal mislukte verificaties: <white>%total_failed%'
- ' <dark_aqua>▪ <gray>Server-uptime: <white>%uptime%'
# Formaat van het bericht over CPU-statistieken
cpu:
- ' <dark_aqua>▪ <gray>Verwerk het CPU-gebruik nu: <white>%process_cpu%%'
- ' <dark_aqua>▪ <gray>Systeem-CPU-gebruik nu: <white>%system_cpu%%'
- ' <dark_aqua>▪ <gray>CPU-gebruik per kernproces: <white>%average_process_cpu%%'
- ' <dark_aqua>▪ <gray>CPU-gebruik per core systeem: <white>%average_system_cpu%%'
- ' <dark_aqua>▪ <gray>Algemene systeembelasting: <white>%load_average%%'
- ' <dark_aqua>▪ <gray>Totaal aantal virtuele CPUs: <white>%virtual_cores%'
# Formaat van het bericht met geheugenstatistieken
memory:
- ' <dark_aqua>▪ <gray>Totaal vrij geheugen: <white>%free_memory%'
- ' <dark_aqua>▪ <gray>Totaal gebruikt geheugen: <white>%used_memory%'
- ' <dark_aqua>▪ <gray>Totaal maximaal geheugen: <white>%max_memory%'
- ' <dark_aqua>▪ <gray>Totaal toegewezen geheugen: <white>%total_memory%'
# Formaat van het bericht met netwerkstatistieken
network:
- ' <dark_aqua>▪ <gray>Huidige inkomende gebruikte bandbreedte: <white>%incoming%'
- ' <dark_aqua>▪ <gray>Huidige uitgaande gebruikte bandbreedte: <white>%outgoing%'
- ' <dark_aqua>▪ <gray>Totale inkomende gebruikte bandbreedte: <white>%ttl_incoming%'
- ' <dark_aqua>▪ <gray>Totale uitgaande gebruikte bandbreedte: <white>%ttl_outgoing%'
# Vertalingen voor alle berichten tijdens het verificatieproces
verification:
# Vertalingen voor alle debug-berichten tijdens de verificatie
logs:
# Bericht dat in de console wordt geregistreerd wanneer een nieuwe speler zich bij de server voegt
connection: '%name%%ip% (%protocol%) heeft verbinding gemaakt.'
# Bericht dat in de console wordt geregistreerd wanneer de verificatie van een speler mislukt
failed: '%ip% (%protocol%) heeft de bot-controle niet doorstaan vanwege: %reason%'
# Bericht dat op de console wordt geregistreerd wanneer een speler op de zwarte lijst staat
blacklisted: '%ip% (%protocol%) stond op de zwarte lijst vanwege te veel mislukte pogingen'
# Bericht dat op de console wordt geregistreerd wanneer een speler is geverifieerd
successful: '%name% is succesvol geverifieerd (%time%s!).'
# Berichten die aan de speler worden getoond wanneer hij of zij de CAPTCHA moet invoeren
captcha:
# Bericht dat aan de speler wordt getoond wanneer hij het antwoord op de captcha moet invoeren
enter-code: '%prefix%<green>Voer de code in de chat in die wordt weergegeven op
de kaart.'
# Bericht dat aan de speler wordt getoond wanneer hij of zij het verkeerde antwoord invoert in de chat
incorrect: '%prefix%<red>U heeft de verkeerde code ingevoerd. Probeer het opnieuw.'
# Verbindingsbericht dat wordt getoond als iemand zich aansluit terwijl de captcha nog niet is voorbereid
currently-preparing:
- '%header%'
- <yellow>Uw anti-bot gegevens zijn nog niet voorbereid.
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand tijdens de verificatie te snel opnieuw deelneemt
too-fast-reconnect:
- '%header%'
- <gold>Je hebt te snel opnieuw verbinding gemaakt. Probeer het later opnieuw.
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt, maar zich al aan het verifiëren is
already-verifying:
- '%header%'
- <red>Uw IP-adres wordt momenteel geverifieerd.
- <red>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt, maar al in de wachtrij staat voor verificatie
already-queued:
- '%header%'
- <red>Uw IP-adres staat momenteel in de wachtrij voor verificatie.
- <red>Wacht een paar minuten voordat u opnieuw probeert te verifiëren.
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand lid wordt, maar tijdelijk op de zwarte lijst staat
blacklisted:
- '%header%'
- <red>Momenteel wordt u de toegang tot de server ontzegd.
- <red>Wacht een paar minuten voordat u weer verbinding kunt maken met de server.
- <gold>Vals positief? <gray>%support-url%
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt met een ongeldige gebruikersnaam
invalid-username:
- '%header%'
- <red>Uw gebruikersnaam bevat ongeldige tekens.
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt met een versie op de zwarte lijst
blacklisted-protocol:
- '%header%'
- <red>U gebruikt een versie die niet is toegestaan op onze server.
- <gold>Hulp nodig bij het inloggen? <gray>%support-url%
- '%footer%'
# Verbindingsbericht dat wordt weergegeven wanneer iemand succesvol verifieert
success:
- '%header%'
- <green>U heeft de verificatie succesvol doorlopen.
- <white>Wanneer u opnieuw verbinding maakt, kunt u nu op de server spelen.
# Verbindingsbericht dat wordt weergegeven wanneer iemand de verificatie mislukt
failed:
- '%header%'
- <red>De bot-verificatie is mislukt.
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
- <gold>Hulp nodig? <gray>%support-url%
- '%footer%'
# Vertalingen voor Sonar's actiebalk uitgebreide berichten
verbose:
layout:
# Lay-out voor de actiebalk van Sonar, uitgebreid
# Tijdelijke aanduidingen:
# - '%queued%' Aantal verbindingen in de wachtrij
# - '%verifying%' Aantal verifiërende verbindingen
# - '%blacklisted%' Aantal op de zwarte lijst geplaatste IP-adressen
# - '%total-joins%' Aantal totale pogingen tot deelname
# - '%verify-total%' Aantal totale verificatiepogingen
# - '%verify-success%' Aantal geverifieerde IP-adressen
# - '%verify-failed%' Aantal mislukte verificaties
# - '%logins-per-second%' Aantal logins per seconde
# - '%connections-per-second%' Aantal verbindingen per seconde
# - '%attack-duration%' Duur van de huidige aanval
# - '%incoming-traffic%' Inkomend bandbreedtegebruik per seconde
# - '%outgoing-traffic%' Uitgaand bandbreedtegebruik per seconde
# - '%incoming-traffic-ttl%' Totaal inkomend bandbreedtegebruik
# - '%outgoing-traffic-ttl%' Totaal uitgaand bandbreedtegebruik
# - '%used-memory%' Hoeveelheid gebruikt geheugen van het proces
# - '%total-memory%' Hoeveelheid totale geheugen van het proces
# - '%max-memory%' Hoeveelheid maximaal geheugen van het proces
# - '%free-memory%' Hoeveelheid vrij geheugen van het proces
# - '%animation%' Aanpasbaar geanimeerd symbool
# Vertalingen voor de normale uitgebreide uitvoer van Sonar
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Inloggen/s
<white>%logins-per-second% <dark_aqua>╺ <gray>Verifiëren <white>%verifying% <dark_aqua>|
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# Layout for Sonar's actionbar verbose during an attack
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Inloggen/s
<white>%logins-per-second% <dark_aqua>╺ <gray>Wachtrij <white>%queued% <dark_aqua>╺
<gray>Verifiëren <white>%verifying% <dark_aqua>╺ <gray>Duur <white>%attack-duration%
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# Animatie voor de actiebalk
# Alternatieven:
# - ▙, ▛, ▜, ▟
# - ⬈, ⬊, ⬋, ⬉
animation:
- ◜
- ◝
- ◞
- ◟
# Vertalingen voor Sonar's aanval meldingen
notifications:
# Lay-out voor de titel van de aanval meldingen van Sonar
title: <yellow><b>Sonar<reset>
# Lay-out voor de ondertitel van Sonar's aanval meldingen
subtitle: <gray>Er is een bot-aanval gedetecteerd
# Lay-out voor de aanval meldingen van Sonar in de chat
chat:
- ''
- <yellow><b>Sonar<reset>
- ''
- <gray>Er is een bot-aanval gedetecteerd.
- ' <dark_aqua>▪ <gray>Inloggen per seconde: <white>%logins-per-second%'
- ' <dark_aqua>▪ <gray>Spelers verifiëren: <white>%verifying%'
- ' <dark_aqua>▪ <gray>Spelers in de wachtrij: <white>%queued%'
- ''
- <gray>Bekijk meer informatie via <white><click:run_command:'/sonar verbose'><hover:show_text:'(Klik
om uit te voeren)'>/sonar verbose</hover></click><gray> of <white><click:run_command:'/sonar
stats'><hover:show_text:'(Klik om uit te voeren)'>/sonar stats</hover></click><gray>.
- ''
@@ -185,10 +185,6 @@ verification:
captcha:
# Сообщение, которое показывается игроку, когда он должен ввести ответ на капчу
enter-code: '%prefix%<green>Введите код в чате, который отображается на карте.'
# Таймер, который показывается игроку, когда он должен ввести капчу
# (Установите это значение на '' что-бы отключить эту функцию)
action-bar: '%prefix%<green>У вас осталось %time-left% секунд, чтобы ввести код
в чате'
# Сообщение, которое показывается игроку, когда он вводит неправильную капчу в чат
incorrect: '%prefix%<red>Вы ввели неправильный код. Пожалуйста, попробуйте еще раз.'
@@ -176,9 +176,6 @@ verification:
captcha:
# Captcha'nın cevabını girmeleri gerektiğinde oyuncuya gösterilen mesaj
enter-code: '%prefix%<green>Haritada gösterilen kodu lütfen sohbete girin.'
# Timer that is shown to the player when they have to enter the answer to the captcha
# (Set this to '' to disable the action bar message)
action-bar: '%prefix%<green>Kodu girmek için %time-left% saniyeniz kaldı'
# Message that is shown to the player when they enter the wrong answer in chat
incorrect: '%prefix%<red>Yanlış kodu girdiniz. Lütfen tekrar deneyin.'
@@ -0,0 +1,303 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# 有问题或需要帮助? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
# %prefix% 占位符
prefix: <b><yellow>Sonar</b> <gray>» <white>
# %support-url% 占位符
support-url: https://jonesdev.xyz/discord/
# %header% 占位符. (支持多行)
header:
- <b><yellow>Sonar</b><reset>
- ''
# %footer% 占位符. (支持多行)
footer:
- <gray>如果您认为这是一个错误, 请联系服务器管理员.
# 当玩家尝试加入但有太多在线玩家同时使用者玩家的 IP 地址时显示的踢出消息.
too-many-online-per-ip:
- '%header%'
- <red>您所使用的 IP 地址已经无法再登录更多账户了.
- '%footer%'
# 有关命令的语言配置.
commands:
# 当玩家尝试在没有权限的情况下运行 /sonar 时显示的消息
no-permission: '%prefix%<red>您没有足够的权限使用该命令.'
# 当命令用法不正确时显示的消息
incorrect-usage: '%prefix%<red>用法: /sonar %usage%'
# 当提供无效的 IP 地址时显示的消息
invalid-ip-address: '%prefix%无法正确识别您提供的IP地址 请检查输入是否有误.'
# 当控制台尝试执行只有玩家能执行的命令时显示的消息
player-only: '%prefix%<red>只有玩家才可以执行该命令.'
# 当玩家尝试执行只有控制台能执行的命令时显示的消息
console-only: '%prefix%<red>出于安全考虑 只允许通过控制台执行该命令.'
# 玩家执行Sonar命令的速度过快时显示的消息
cool-down: '%prefix%<red>您只能每 0.5 秒执行一次此命令.'
cool-down-left: '%prefix%<red>请等待 <bold>%time-left% 秒<reset><red> 后执行该命令.'
# 当玩家没有权限执行某个子命令时显示的消息
subcommand-no-permission: '%prefix%<red>您没有足够的权限使用该子命令. <gray>(%permission%)'
# 根命令
main:
# 执行根命令时显示在所有输出内容(由该命令输出的消息)上方的消息
header:
- <yellow>正在在 %platform% 平台上运行 <b>Sonar</b> %version%.
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
Jones Development and Sonar Contributors</click>
- ''
- <gray>有问题或需要帮助?
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click
to open Discord)'>在我们的Discord上打开一张ticket </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click
to open GitHub)'>或在Github打开新的问题.
- ''
# 执行根命令时 对于子命令的描述.
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>只有玩家可以执行该命令:
</gray>%only_players%<br><gray>只有控制台可以执行该命令: </gray>%require_console%<br><gray>需要的权限:
</gray><white>%permission%<br><gray>别名: </gray>%aliases%''><dark_aqua>
▪ <gray>/sonar %subcommand% <white>%description%'
# 重新加载命令
reload:
# 当有人开始重载插件时显示的消息
start: '%prefix%正在重新加载 Sonar...'
# 插件完成重载时显示的消息
finish: '%prefix%<green>成功重新加载配置文件. <gray>(花费 %taken% 毫秒)'
# '/sonar verbose' 命令的消息
verbose:
# 玩家订阅详细信息时显示的消息
subscribed: '%prefix%已为您显示 Sonar 的详细信息.'
# 玩家取消订阅详细信息时显示的消息
unsubscribed: '%prefix%不再为您显示 Sonar 的详细信息.'
# '/sonar notify' 命令的消息
notify:
# 玩家订阅攻击通知时显示的消息
subscribed: '%prefix%已订阅攻击通知. 当服务器受到攻击时将会提醒您.'
# 玩家取消订阅攻击通知时显示的消息
unsubscribed: '%prefix%已退订攻击通知'
# '/sonar blacklist' 命令的消息
blacklist:
# 当尝试清理黑名单但黑名单中没有任何 IP 地址显示的消息
empty: '%prefix%黑名单中没有任何 IP 地址.'
# 当成功清理黑名单时显示的消息
cleared: '%prefix%您已成功从黑名单中删除 %removed% 个 IP 地址.'
# 当查看黑名单的大小时显示的消息
size: '%prefix%黑名单中目前有 %amount% 个 IP 地址.'
# 当手动将一个 IP 地址添加到黑名单时显示的消息
added: '%prefix%成功将 IP 地址 %ip% 列入黑名单.'
# 当尝试将白名单中的 IP 地址添加到黑名单中时显示的警告消息
added-warning: '%prefix%<red>警告: <white>IP 地址 %ip% 在白名单中. 添加到黑名单则意味着它将被同时从白名单中移除.'
# 当尝试从黑名单中移除一个 IP 地址时显示的消息
removed: '%prefix%成功将 IP 地址 %ip% 移出黑名单.'
# 当尝试向黑名单添加一个已经在黑名单中的 IP 地址时显示的消息
duplicate-ip: '%prefix%该 IP 地址已经在黑名单里了.'
# 当尝试移除一个不在黑名单中的 IP 地址时显示的消息
ip-not-found: '%prefix%您提供的 IP 地址并不在黑名单中.'
# '/sonar verified' 命令的消息
verified:
# 当尝试清除白名单但为空时显示的消息
empty: '%prefix%白名单中没有任何玩家.'
# 当成功清理白名单时显示的消息
cleared: '%prefix%您已成功从白名单中删除 %removed% 个玩家.'
# 当查看白名单的大小时显示的消息
size: '%prefix%白名单中目前有 %amount% 个已验证的玩家.'
# 当成功将一个 IP 地址内所有已验证的玩家移出白名单时显示的消息
removed: '%prefix%成功将所有使用 IP 地址 (%ip%) 的玩家移出白名单.'
# 当手动将一个 IP 地址添加到白名单中显示的消息
added: '%prefix%成功将 IP 地址 %ip% 列入白名单.'
# 当尝试添加一个已经在白名单中的 IP 地址和玩家的UUID 时显示的消息
already: '%prefix%您提供的 IP 地址和玩家的UUID已经在白名单中了.'
# 当尝试移除一个不在白名单中的 IP 地址时显示的消息
ip-not-found: '%prefix%您提供的 IP 地址不在白名单中.'
# 当操作冲突时(例如同时将一个 IP 地址移除白名单)显示的消息
blocked: '%prefix%请等待当前操作完成.'
# '/sonar statistics' 命令的消息
statistics:
# 执行该命令时显示在所有输出内容(由该命令输出的消息)上方的消息
header: '%prefix%当前会话中的 %type% 类型的统计信息:'
# 当查看一个未知的统计信息时显示的消息
unknown-type: '%prefix%<red>未知的统计信息! 可用的统计信息: <gray>%statistics%'
# 默认统计信息的消息
general:
- ' <dark_aqua>▪ <gray>白名单中的 IP 地址数量: <white>%verified%'
- ' <dark_aqua>▪ <gray>正在进行验证的 IP 地址数量: <white>%verifying%'
- ' <dark_aqua>▪ <gray>黑名单中的 IP 地址数量: <white>%blacklisted%'
- ' <dark_aqua>▪ <gray>缓冲队列中的等待登录的数量: <white>%queued%'
- ' <dark_aqua>▪ <gray>玩家加入次数: <white>%total_joins%'
- ' <dark_aqua>▪ <gray>总验证次数: <white>%total_attempts%'
- ' <dark_aqua>▪ <gray>验证失败的次数: <white>%total_failed%'
- ' <dark_aqua>▪ <gray>服务器当前会话的运行时间: <white>%uptime%'
# CPU 统计信息
cpu:
- ' <dark_aqua>▪ <gray>当前进程的 CPU 使用率: <white>%process_cpu%%'
- ' <dark_aqua>▪ <gray>当前系统的 CPU 使用率: <white>%system_cpu%%'
- ' <dark_aqua>▪ <gray>当前进程的多核平均使用率: <white>%average_process_cpu%%'
- ' <dark_aqua>▪ <gray>当前系统的多核平均使用率: <white>%average_system_cpu%%'
- ' <dark_aqua>▪ <gray>系统平均负载: <white>%load_average%%'
- ' <dark_aqua>▪ <gray>虚拟核心数量: <white>%virtual_cores%'
# 内存统计信息
memory:
- ' <dark_aqua>▪ <gray>可用内存: <white>%free_memory%'
- ' <dark_aqua>▪ <gray>已使用的内存: <white>%used_memory%'
- ' <dark_aqua>▪ <gray>最大可用内存: <white>%max_memory%'
- ' <dark_aqua>▪ <gray>已分配的内存: <white>%total_memory%'
# 网络统计信息
network:
- ' <dark_aqua>▪ <gray>当前下行: <white>%incoming%'
- ' <dark_aqua>▪ <gray>当前上行: <white>%outgoing%'
- ' <dark_aqua>▪ <gray>下行总量: <white>%ttl_incoming%'
- ' <dark_aqua>▪ <gray>上行总量: <white>%ttl_outgoing%'
# 验证过程中所有消息的翻译
verification:
# 验证过程中向控制台打印的调试信息
logs:
# 当玩家进入服务器并验证时显示的消息
connection: '%name%%ip% (%protocol%) 连接到了服务器.'
# 当玩家未通过检查时打印的消息
failed: '%ip% (%protocol%) 由于原因 %reason% 无法通过检查.'
# 当玩家被自动列入黑名单时打印的消息
blacklisted: '%ip% (%protocol%) 因太多次无法完成验证而被列入黑名单.'
# 当玩家完成验证时打印的消息
successful: '%name% 成功通过了验证 (耗时 %time% 秒!).'
# 当验证中的玩家需要解决验证码时显示的消息
captcha:
# 玩家需要输入验证码答案时显示的消息
enter-code: '%prefix%<green>请在聊天框中发送地图上的验证码.'
# 当玩家输入错误的验证码时显示的消息
incorrect: '%prefix%<red>您输入了错误的验证码. 请再试一次'
# 当验证码还在准备的时候显示的踢出消息
currently-preparing:
- '%header%'
- <yellow>反机器人还在准备所需的数据
- <gray>请稍后重新加入服务器
- '%footer%'
# 当玩家需要验证时重新连接速度太快时显示的踢出消息
too-fast-reconnect:
- '%header%'
- <gold>您的重新连接速度过快
- <gray>请稍后重新加入服务器
- '%footer%'
# 当玩家尝试加入但它的 IP 已经有另一位玩家在验证时显示的踢出消息
already-verifying:
- '%header%'
- <red>有另一位玩家正在使用您当前的 IP 地址进行验证
- <red>请稍后重新加入服务器
- '%footer%'
# 已经在排队等待验证的人加入时显示的踢出消息
already-queued:
- '%header%'
- <red>您的 IP 地址当前正在排队等待验证
- <red>请稍后重新加入服务器
- '%footer%'
# 当玩家尝试加入但暂时被列入黑名单时显示的踢出消息
blacklisted:
- '%header%'
- <red>您已被临时禁止加入服务器
- <red>请等待几分钟后重新加入服务器
- <gold>您认为您不应该被禁止? <gray>%support-url%
- '%footer%'
# 当玩家尝试加入 但他们的用户名包含不允许的字符时显示的踢出消息
invalid-username:
- '%header%'
- <red>您的用户名包含无效的字符. 请更改您的用户名 然后再试.
- '%footer%'
# 当玩家使用列入黑名单的版本加入时显示的踢出消息
blacklisted-protocol:
- '%header%'
- <red>您当前所使用的协议/客户端版本已被服务器禁止
- <gold>需要帮助以便您能登录服务器? <gray>%support-url%
- '%footer%'
# 当玩家验证成功时显示的踢出消息
success:
- '%header%'
- <green>您已成功通过验证
- <white>请重新连接以加入服务器
# 当玩家未能通过验证时显示的消息
failed:
- '%header%'
- <red>无法验证您是否是机器人
- <gray>请等待几秒后重新加入服务器以重新验证
- <gold>需要帮助? <gray>%support-url%
- '%footer%'
# 状态栏详细信息
verbose:
layout:
# 详细信息状态栏样式
# 占位符:
# - '%queued%' 正在排队的连接数数量
# - '%verifying%' 正在进行验证的连接数数量
# - '%blacklisted%' 黑名单大小
# - '%total-joins%' 玩家加入次数
# - '%verify-total%' 已执行的验证次数
# - '%verify-success%' 成功验证的 IP 的数量
# - '%verify-failed%' 验证失败的次数
# - '%logins-per-second%' 每秒登录服务器次数
# - '%connections-per-second%' 每秒接受连接次数
# - '%attack-duration%' 攻击持续时间
# - '%incoming-traffic%' 每秒下行带宽
# - '%outgoing-traffic%' 每秒上行带宽
# - '%incoming-traffic-ttl%' 已使用的下行带宽
# - '%outgoing-traffic-ttl%' 已使用的上行带宽
# - '%used-memory%' 已使用的内存
# - '%total-memory%' 已分配的内存
# - '%max-memory%' 最大可用内存
# - '%free-memory%' 可用内存
# - '%animation%' 可自定义的动画符号
# 默认的状态栏
normal: '%prefix%<gray>每秒连接数 <white>%connections-per-second% <dark_aqua>╺ <gray>每秒登录次数
<white>%logins-per-second% <dark_aqua>╺ <gray>正在验证的连接数 <white>%verifying% <dark_aqua>|
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# 遭受攻击时的状态栏
attack: '%prefix%<gray>每秒连接数 <white>%connections-per-second% <dark_aqua>╺ <gray>每秒登录次数
<white>%logins-per-second% <dark_aqua>╺ <gray>排队连接数 <white>%queued% <dark_aqua>╺
<gray>正在验证的连接数 <white>%verifying% <dark_aqua>╺ <gray>攻击持续时间 <white>%attack-duration%
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
# 动画占位符
# 其它预设:
# - ▙, ▛, ▜, ▟
# - ⬈, ⬊, ⬋, ⬉
animation:
- ◜
- ◝
- ◞
- ◟
# 攻击通知
notifications:
# 标题
title: <yellow><b>Sonar<reset>
# 子标题
subtitle: <gray>检测到活动的机器人攻击..
# 聊天消息
chat:
- ''
- <yellow><b>Sonar<reset>
- ''
- <gray>检测到活动的机器人攻击
- ' <dark_aqua>▪ <gray>每秒登录次数: <white>%logins-per-second%'
- ' <dark_aqua>▪ <gray>正在验证的连接数: <white>%verifying%'
- ' <dark_aqua>▪ <gray>排队连接数: <white>%queued%'
- ''
- <gray>使用 <white><click:run_command:'/sonar verbose'><hover:show_text:'(
- 点击执行)'>/sonar verbose</hover></click><gray> 或 <white><click:run_command:'/sonar
stats'><hover:show_text:'(点击执行)'>/sonar stats</hover></click><gray> 来查看更多信息.
- ''
@@ -0,0 +1,60 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
#
# Les notifications d'attaque bot peuvent être envoyé à discord via un webhook
webhook:
# Lien du webhook discord (Mettez '' pour désactiver les webhooks)
url: ''
# Nom d'utilisateur du webhook
username: Sonar
# Lien vers l'avatar du webhook (Mettez '' pour désactiver)
avatar-url: ''
# Message à envoyer (Mettez '' pour désactiver)
# Vous pouvez mentionner des personnes en utilisant <@idUtilisateur>
# Si vous voulez mentionner un rôle vous devriez utiliser <@&idRole>
content: ''
# Embed qui sera envoyé quand il y a une attaque
embed:
# Petit footer de l'embed
footer:
# Message du footer
text: © Jones Development et Contributeurs Sonar
# Lien de l'icône du footer
icon-url: ''
# Titre du webhook discord
title: '✅ Attaque mitigé'
# Lien clickable du titre
title-url: ''
# Description (message) de l'embed
description:
- 'L''attaque sur votre serveur a été mitigé'
- ''
- 'Démarré à: <t:%start-timestamp%:T>'
- 'Fini à: <t:%end-timestamp%:T>'
- 'Durée: %duration%'
- ''
- 'Pique du processeur: %peak-cpu%%'
- 'Pique de la mémoire ram: %peak-memory%'
- 'Pique du nombre de bots par secondes: %peak-bps%'
- ''
- "IP Blacklisté durant l'attaque: %total-blacklisted%"
- "Vérification échoué durant l'attaque: %total-failed%"
- "Vérification réussi durant l'attaque: %total-success%"
# Code couleur RGB pour l'embed
# Sélectionner la couleur: https://www.rapidtables.com/web/color/RGB_Color.html
color:
red: 0
green: 255
blue: 0
@@ -0,0 +1,60 @@
#
# /$$$$$$
# /$$__ $$
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
# \______/ \______/ |__/ |__/ \_______/|__/
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
# Bot aanval meldingen kunnen ook met behulp van webhooks naar uw Discord-server worden verzonden
webhook:
# URL van de Discord-webhook (zet dit op '' om webhooks uit te schakelen)
url: ''
# Gebruikersnaam van de Discord-webhook-afzender
username: Sonar
# URL naar de avatar van de Discord-webhook-afzender (zet dit op '' om uit te schakelen)
avatar-url: ''
# Inhoud van het Discord-webhook (zet dit op '' om uit te schakelen)
# U kunt dit gebruiken om bv. ping medewerkers met <@userId>
# Als u rollen wilt pingen, moet u <@&roleId> gebruiken
content: ''
# Embed Discord-webhook dat wordt verzonden wanneer een aanval is gestopt
embed:
# Klein voettekst bericht van de Discord-webhook-insluiting
footer:
# Inhoud van de voettekst bericht van de Discord-webhook-insluiting
text: © Jones Development and Sonar Contributors
# URL van de voettekst pictogram van de Discord-webhook-insluiting
icon-url: ''
# Titel van de Discord-webhook-insluiting
title: ':white_check_mark: Aanval verzacht'
# Klikbare URL van de titel van de Discord-webhook-insluiting
title-url: ''
# Beschrijving (inhoud) van de Discord-webhook-insluiting
description:
- De aanval op uw server is afgezwakt.
- ''
- 'Aanval beginnen: <t:%start-timestamp%:T>'
- 'Einde aanval: <t:%end-timestamp%:T>'
- 'Duur van de aanval: %duration%'
- ''
- 'Maximaal proces CPU-gebruik tijdens de aanval: %peak-cpu%%'
- 'Maximaal proces-geheugengebruik tijdens de aanval: %peak-memory%'
- 'Maximaal bots per seconde tijdens de aanval: %peak-bps%'
- ''
- 'IP-adressen op de zwarte lijst gezet tijdens de aanval: %total-blacklisted%'
- 'Mislukte verificaties tijdens de aanval: %total-failed%'
- 'Succesvolle verificaties tijdens de aanval: %total-success%'
# RGB-kleuren van de Discord-webhook-insluiting
# Kleur kiezer: https://www.rapidtables.com/web/color/RGB_Color.html
color:
red: 0
green: 255
blue: 0
@@ -25,27 +25,24 @@ import org.bstats.charts.SimplePie;
import org.bstats.velocity.Metrics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
import xyz.jonesdev.sonar.velocity.command.VelocitySonarCommand;
import xyz.jonesdev.sonar.velocity.fallback.FallbackInjectionHelper;
import java.net.InetAddress;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter
public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
public static SonarVelocity INSTANCE;
public SonarVelocity(final @NotNull SonarVelocityPlugin plugin) {
super(plugin, new VelocityLibraryManager<>(
plugin, plugin.getLogger(), plugin.getDataDirectory(), plugin.getServer().getPluginManager()),
plugin.getDataDirectory().toFile(), SonarPlatform.VELOCITY);
super(plugin, SonarPlatform.VELOCITY, plugin.getDataDirectory().toFile(),
new VelocityLibraryManager<>(plugin, plugin.getLogger(),
plugin.getDataDirectory(), plugin.getServer().getPluginManager()));
INSTANCE = this;
}
@@ -112,28 +109,15 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
// Register Sonar command
getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand());
// Register traffic service
getPlugin().getServer().getScheduler().buildTask(getPlugin(), CachedBandwidthStatistics::reset)
.repeat(1L, TimeUnit.SECONDS)
.schedule();
// Register queue service
getPlugin().getServer().getScheduler().buildTask(getPlugin(), getFallback().getQueue().getPollTask())
.repeat(500L, TimeUnit.MILLISECONDS)
.schedule();
// Register verbose service
getPlugin().getServer().getScheduler().buildTask(getPlugin(), Sonar.get().getVerboseHandler()::update)
.repeat(200L, TimeUnit.MILLISECONDS)
.schedule();
// Make sure to inject into the server's connection handler
FallbackInjectionHelper.inject(getPlugin().getServer());
}
@Override
public void disable() {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
if (metrics != null) {
// Make sure to properly shutdown bStats metrics
metrics.shutdown();
}
}
}
@@ -22,25 +22,12 @@ import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.FallbackBandwidthHandler;
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandlerAdapter;
import xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.utility.geyser.GeyserUtil;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import static com.velocitypowered.proxy.network.Connections.*;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter {
@@ -48,137 +35,22 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
super(channel);
}
private @ApiStatus.Internal HandshakePacket handshakePacket;
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
// Intercept any handshake packet by the client
if (msg instanceof HandshakePacket handshake) {
handleHandshake(handshake);
handleHandshake(handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
}
// Intercept any server login packet by the client
if (msg instanceof ServerLoginPacket serverLogin) {
handleLogin(ctx, serverLogin);
// Make sure to use the potentially modified, original IP
final MinecraftConnection minecraftConnection = (MinecraftConnection) channel.pipeline().get(HANDLER);
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
handleLogin(ctx, serverLogin, serverLogin.getUsername(), socketAddress,
MINECRAFT_ENCODER, MINECRAFT_DECODER, READ_TIMEOUT, HANDLER);
return;
}
// Make sure to let the server handle the rest
ctx.fireChannelRead(msg);
}
private void handleHandshake(final @NotNull HandshakePacket handshake) throws Exception {
// Check if the player has already sent a handshake packet
if (handshakePacket != null) {
throw new CorruptedFrameException("Already sent handshake");
}
// Check if the hostname is invalid
if (handshake.getServerAddress().isEmpty()) {
throw new CorruptedFrameException("Hostname is empty");
}
// Store the protocol version
protocolVersion = ProtocolVersion.fromId(handshake.getProtocolVersion().getProtocol());
// Connections from unknown protocol versions will be discarded
// as this is the safest way of handling unwanted connections
if (protocolVersion.isUnknown()) {
// Sonar does NOT support snapshots or unknown versions;
// I'll try my best to stay up-to-date!
throw new CorruptedFrameException("Unknown protocol");
}
// Store the handshake packet
handshakePacket = handshake;
// Hook the traffic listener
// TODO: Can we implement this in channelActive?
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
private void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull ServerLoginPacket serverLogin) throws Exception {
// Check if the player has already sent a login packet
if (username != null) {
throw new CorruptedFrameException("Already logged on");
}
// Increase joins per second for the action bar verbose
GlobalSonarStatistics.countLogin();
// Store the username
username = serverLogin.getUsername();
// Make sure to use the potentially modified, original IP
final MinecraftConnection minecraftConnection = (MinecraftConnection) channel.pipeline().get(HANDLER);
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
inetAddress = socketAddress.getAddress();
// Check the blacklist here since we cannot let the player "ghost join"
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, blacklisted, MINECRAFT_ENCODER, HANDLER);
return;
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
return;
}
// Completely skip Geyser connections
if (GeyserUtil.isGeyserConnection(channel, socketAddress)) {
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
return;
}
// Check if the player is already queued since we don't want bots to flood the queue
if (FALLBACK.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyQueued, MINECRAFT_ENCODER, HANDLER);
return;
}
// Check if Fallback is already verifying a player
// → is another player with the same IP address connected to Fallback?
if (FALLBACK.getConnected().containsKey(serverLogin.getUsername())
|| FALLBACK.getConnected().containsValue(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying, MINECRAFT_ENCODER, HANDLER);
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())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted, MINECRAFT_ENCODER, HANDLER);
return;
}
// Make sure we actually have to verify the player
final String offlineUUIDString = "OfflinePlayer:" + serverLogin.getUsername();
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
return;
}
// Check if the IP address is currently being rate-limited
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast, MINECRAFT_ENCODER, HANDLER);
return;
}
// Check if the protocol ID of the player is allowed to bypass verification
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
.contains(protocolVersion.getProtocol())) {
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
return;
}
// Queue the connection for further processing
FALLBACK.getQueue().queue(inetAddress, () -> channel.eventLoop().execute(() -> {
// Check if the username matches the valid name regex to prevent
// UTF-16 names or other types of exploits
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
.matcher(serverLogin.getUsername()).matches()) {
customDisconnect(channel, protocolVersion, invalidUsername, MINECRAFT_ENCODER, HANDLER);
return;
}
// Create an instance for the Fallback connection
user = new FallbackUserWrapper(channel, inetAddress, protocolVersion);
// Let the verification handler take over the channel
user.hijack(serverLogin.getUsername(), offlineUUID, MINECRAFT_ENCODER, MINECRAFT_DECODER, READ_TIMEOUT, HANDLER);
}));
}
}
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more