mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 09:20:15 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bea1d292ee | ||
|
|
468a11e0be | ||
|
|
cf2fce9c29 | ||
|
|
cc52289500 | ||
|
|
ac1f6d616b | ||
|
|
bdd941171e | ||
|
|
1e18a29d1e | ||
|
|
5671c2029e | ||
|
|
32aeee4f06 | ||
|
|
7b5ce41420 | ||
|
|
a66f60f177 | ||
|
|
326a1ecc07 | ||
|
|
6af5a9fc4b | ||
|
|
ac78d88721 | ||
|
|
75abebde7c | ||
|
|
28b1615b78 | ||
|
|
600275669b | ||
|
|
0c3761be11 | ||
|
|
b7d8ec74d0 | ||
|
|
96efa574e0 | ||
|
|
8db3d0257f | ||
|
|
106a44f78a | ||
|
|
a5872f1b6e | ||
|
|
e9c212b9e4 | ||
|
|
42da141f2b | ||
|
|
e0bc83f8ef | ||
|
|
59822ef541 | ||
|
|
38fbfff896 | ||
|
|
6725975cf1 | ||
|
|
d59fd4b431 | ||
|
|
a76ca2f91d | ||
|
|
30803ba6e4 | ||
|
|
194232804b | ||
|
|
19c97b26b6 | ||
|
|
d66719b3e0 | ||
|
|
9226908002 | ||
|
|
ac74f14dc0 | ||
|
|
8f11a53b38 | ||
|
|
0b4394624f | ||
|
|
d8f7e288e8 | ||
|
|
3f3411ca9d | ||
|
|
14ae85384a | ||
|
|
8204477eb6 | ||
|
|
44754e5027 | ||
|
|
847936ed08 |
No files matched your search
+15
-23
@@ -9,6 +9,7 @@
|
||||
|
||||
<!-- Badges & icons -->
|
||||
[](https://github.com/jonesdevelopment/sonar/actions/workflows/gradle.yml)
|
||||
[](https://www.codefactor.io/repository/github/jonesdevelopment/sonar/overview/main)
|
||||
[](https://github.com/jonesdevelopment/sonar/releases)
|
||||
[](https://github.com/jonesdevelopment/sonar/issues)
|
||||
[](https://jonesdev.xyz/discord)
|
||||
@@ -24,7 +25,7 @@
|
||||
|
|
||||
[Discord](https://jonesdev.xyz/discord)
|
||||
|
|
||||
[License](https://github.com/jonesdevelopment/sonar/blob/main/README.md#license)
|
||||
[License](https://github.com/jonesdevelopment/sonar/?tab=readme-ov-file#license)
|
||||
</div>
|
||||
|
||||
## Design and Goal
|
||||
@@ -32,27 +33,25 @@
|
||||
* Effective, lightweight, and easy-to-use
|
||||
* No unnecessary features and clean code
|
||||
* Protection against all kinds of bot attacks
|
||||
* No player should be annoyed by any sort of captcha
|
||||
* No player should be annoyed by any sort of [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA)
|
||||
* No sort of checking for VPNs or proxies
|
||||
* Multi-platform support (Velocity, BungeeCord, and Bukkit)
|
||||
* Multi-platform support (See [supported versions](https://docs.jonesdev.xyz/sonar/supported-versions))
|
||||
|
||||
## Checks
|
||||
|
||||
- New player logins are queued to prevent spam login attacks.
|
||||
- [Fallback](https://github.com/jonesdevelopment/sonar#fallback) is Sonar's main component designed to prevent
|
||||
all types of bots.
|
||||
- Spigot & BungeeCord
|
||||
- Duplicate login/status packet spam fix
|
||||
- Run server ping handling asynchronously
|
||||
- [Fallback](https://github.com/jonesdevelopment/sonar#fallback) is Sonar's main anti-bot component
|
||||
- Sonar queues new player logins to prevent spam login attacks
|
||||
- Protective components only on BungeeCord:
|
||||
- Sonar checks the handshake packets for legitimacy
|
||||
- Sonar validates the login/server ping packets
|
||||
- Sonar handles all server pings asynchronously
|
||||
- The Varint decoder is updated to Velocity's
|
||||
improved [MinecraftVarintFrameDecoder](https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java).
|
||||
|
||||
### Fallback
|
||||
|
||||
Fallback analyzes a player's behavior before joining the actual server, therefore stopping malicious traffic from ever
|
||||
reaching the backend.
|
||||
It is supposed to be an instant, powerful, and invisible method of verification that should prevent all typical and
|
||||
advanced types of bots.
|
||||
Fallback analyzes a player's behavior before joining the actual server, therefore stopping malicious traffic from ever reaching the backend.
|
||||
It is supposed to be an instant, powerful, and invisible method of verification that should prevent all typical and advanced types of bots.
|
||||
|
||||
* Sends the player to a lightweight fake server when they connect for the first time.
|
||||
* Analyzes if the player is sending the necessary packets.
|
||||
@@ -60,16 +59,11 @@ advanced types of bots.
|
||||
* Checks if the player is obeying client gravity.
|
||||
* Checks if the player is colliding with blocks correctly.
|
||||
|
||||
Fallback also protects from huge spambot attacks since it queues the incoming connections, therefore making it
|
||||
technically impossible to have a ton of bots join the server at the same time.
|
||||
Fallback also protects against huge spambot attacks since it queues the incoming connections, therefore making it technically impossible to have a ton of bots join the server at the same time.
|
||||
|
||||
### False positives
|
||||
|
||||
Fallback is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check.
|
||||
However, there are some edge cases where Fallback might not receive packets within the necessary time period. In this case, Sonar tries to account for these edge cases in order to prevent false positives. For example, some higher Minecraft versions have a bug where the client sometimes sends a packet out of order. Sonar accounts for that and does not falsely blacklist clients.
|
||||
If you or one of your players experiences a false positive, make sure to report it by opening
|
||||
a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues) or a ticket
|
||||
on [Discord](https://jonesdev.xyz/discord/).
|
||||
Fallback is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol, which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check. However, there are some edge cases where Fallback might not receive packets within the necessary time period. In this case, Sonar tries to account for these edge cases in order to prevent false positives. If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues) or a ticket on [Discord](https://jonesdev.xyz/discord/).
|
||||
|
||||
## Building
|
||||
|
||||
@@ -79,9 +73,7 @@ You can also take a look at the [gradle documentation](https://docs.gradle.org/c
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing, you can check out
|
||||
the [Contributing Guidelines](https://github.com/jonesdevelopment/sonar/blob/main/.github/CONTRIBUTING.md) for detailed
|
||||
instructions.
|
||||
If you are interested in contributing, you can check out the [Contributing Guidelines](https://github.com/jonesdevelopment/sonar/blob/main/.github/CONTRIBUTING.md) for detailed instructions.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+3
-7
@@ -13,13 +13,9 @@ If you discover a security vulnerability within Sonar, please follow these steps
|
||||
|
||||
## Contact
|
||||
|
||||
If you have any questions or concerns regarding the security of this project, please open a ticket on Discord or contact me via email at admin@jonesdev.xyz.
|
||||
If you have any questions or concerns regarding the security of this project, please open a ticket on Discord or contact me via email at contact@jonesdev.xyz.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to the following people for their contributions:
|
||||
- [xism4](https://github.com/xism4)
|
||||
- fixed serious issues with packet decode logic on BungeeCord [#5](https://github.com/jonesdevelopment/sonar-legacy/pull/5) (Severity: medium)
|
||||
- fixed a false blacklisting issue when a player looses connection [#9](https://github.com/jonesdevelopment/sonar-legacy/pull/9) (Severity: low)
|
||||
- [NoGodHenry](https://github.com/NoGodHenry)
|
||||
- fixed a ConcurrentModificationException in the configuration [#3](https://github.com/jonesdevelopment/sonar-legacy/pull/3) (Severity: low)
|
||||
- Special thanks to the [contributors of Sonar](https://github.com/jonesdevelopment/sonar/graphs/contributors),
|
||||
and also those who reported security issues or bugs.
|
||||
@@ -22,10 +22,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
|
||||
|
||||
+16
-3
@@ -35,7 +35,7 @@ allprojects {
|
||||
testCompileOnly("org.projectlombok:lombok:1.18.30")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
val adventureVersion = "4.14.0"
|
||||
val adventureVersion = "4.15.0"
|
||||
|
||||
// adventure
|
||||
implementation("net.kyori:adventure-text-minimessage:$adventureVersion")
|
||||
@@ -46,7 +46,7 @@ allprojects {
|
||||
implementation("com.j256.ormlite:ormlite-jdbc:6.1") // ORM
|
||||
implementation("xyz.jonesdev:cappuccino:0.1.6-SNAPSHOT") // expiring cache
|
||||
|
||||
compileOnly("io.netty:netty-all:4.1.100.Final") // netty
|
||||
compileOnly("io.netty:netty-all:4.1.104.Final") // netty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,16 +71,29 @@ tasks {
|
||||
// Set the file name of the shadowed jar
|
||||
archiveFileName.set("Sonar.jar")
|
||||
|
||||
// Remove file timestamps
|
||||
isPreserveFileTimestamps = false
|
||||
|
||||
// bStats has to be relocated to the Sonar package otherwise it throws an exception
|
||||
// https://github.com/Bastian/bstats-metrics/blob/master/base/src/main/java/org/bstats/MetricsBase.java#L251
|
||||
relocate("org.bstats", "xyz.jonesdev.sonar.libs.bstats")
|
||||
|
||||
// https://github.com/jonesdevelopment/sonar/issues/46
|
||||
// Relocate some packages, so we don't run into issues where we accidentally use Velocity's classes
|
||||
relocate("net.kyori.adventure.nbt", "xyz.jonesdev.sonar.libs.nbt")
|
||||
relocate("org.simpleyaml", "xyz.jonesdev.sonar.libs.yaml")
|
||||
relocate("com.google.gson", "xyz.jonesdev.sonar.libs.gson")
|
||||
relocate("com.j256.ormlite", "xyz.jonesdev.sonar.libs.ormlite")
|
||||
relocate("xyz.jonesdev.cappuccino", "xyz.jonesdev.sonar.libs.cappuccino")
|
||||
relocate("net.kyori.examination", "xyz.jonesdev.sonar.libs.examination")
|
||||
relocate("net.kyori.option", "xyz.jonesdev.sonar.libs.option")
|
||||
// We have to be careful here, so we don't accidentally break adventure
|
||||
relocate("net.kyori.adventure", "xyz.jonesdev.sonar.libs.adventure") {
|
||||
exclude("net.kyori.adventure.text.**")
|
||||
exclude("net.kyori.adventure.audience.**")
|
||||
}
|
||||
|
||||
// Exclude unnecessary metadata information
|
||||
exclude("META-INF/versions/**")
|
||||
}
|
||||
|
||||
compileJava {
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.8
|
||||
version=2.0.15
|
||||
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -27,6 +27,7 @@ import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEventManager;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
|
||||
import java.io.File;
|
||||
@@ -60,6 +61,8 @@ public interface Sonar {
|
||||
|
||||
@NotNull Verbose getVerboseHandler();
|
||||
|
||||
@NotNull SystemTimer getLaunchTimer();
|
||||
|
||||
/**
|
||||
* Set a custom verbose handler
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -24,6 +24,7 @@ import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.event.impl.AttackDetectedEvent;
|
||||
import xyz.jonesdev.sonar.api.event.impl.AttackMitigatedEvent;
|
||||
import xyz.jonesdev.sonar.api.profiler.JVMProfiler;
|
||||
import xyz.jonesdev.sonar.api.statistics.Counters;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
@@ -43,12 +44,15 @@ public final class AttackTracker implements JVMProfiler {
|
||||
private final SystemTimer duration = new SystemTimer();
|
||||
private final SystemTimer timer = new SystemTimer();
|
||||
private int peakJoinsPerSecond;
|
||||
private int peakConnectionsPerSecond;
|
||||
private double peakProcessCPUUsage;
|
||||
private long peakProcessMemoryUsage;
|
||||
// Calculate during-attack-statistics using their deltas
|
||||
private int successfulVerifications, failedVerifications;
|
||||
}
|
||||
|
||||
public void checkIfUnderAttack() {
|
||||
final int joinsPerSecond = Sonar.get().getVerboseHandler().getLoginsPerSecond().estimatedSize();
|
||||
final int joinsPerSecond = Counters.LOGINS_PER_SECOND.estimatedSize();
|
||||
final int verifyingPlayers = Sonar.get().getFallback().getConnected().size();
|
||||
final int queuedPlayers = Sonar.get().getFallback().getQueue().getQueuedPlayers().size();
|
||||
final int minPlayers = Sonar.get().getConfig().getMinPlayersForAttack();
|
||||
@@ -62,6 +66,8 @@ public final class AttackTracker implements JVMProfiler {
|
||||
// An attack has been detected
|
||||
if (currentAttack == null) {
|
||||
currentAttack = new AttackStatistics();
|
||||
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
currentAttack.failedVerifications = Statistics.FAILED_VERIFICATIONS.get();
|
||||
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
|
||||
} else {
|
||||
// Reset attack timer if we're still under attack
|
||||
@@ -73,6 +79,11 @@ public final class AttackTracker implements JVMProfiler {
|
||||
// Update joins per second peak if necessary
|
||||
currentAttack.peakJoinsPerSecond = joinsPerSecond;
|
||||
}
|
||||
final int connectionsPerSecond = Counters.CONNECTIONS_PER_SECOND.estimatedSize();
|
||||
if (connectionsPerSecond > currentAttack.peakConnectionsPerSecond) {
|
||||
// Update connections per second peak if necessary
|
||||
currentAttack.peakConnectionsPerSecond = connectionsPerSecond;
|
||||
}
|
||||
final double processCPUUsage = getProcessCPUUsage();
|
||||
if (processCPUUsage > currentAttack.peakProcessCPUUsage) {
|
||||
// Update cpu usage peak if necessary
|
||||
@@ -95,14 +106,18 @@ public final class AttackTracker implements JVMProfiler {
|
||||
final String peakCPU = Sonar.DECIMAL_FORMAT.format(currentAttack.peakProcessCPUUsage);
|
||||
final String peakMem = formatMemory(currentAttack.peakProcessMemoryUsage);
|
||||
final String peakBPS = Sonar.DECIMAL_FORMAT.format(currentAttack.peakJoinsPerSecond);
|
||||
final String peakCPS = Sonar.DECIMAL_FORMAT.format(currentAttack.peakConnectionsPerSecond);
|
||||
final long minutes = deltaInMillis / (60 * 1000); // Convert milliseconds to minutes
|
||||
final double seconds = (deltaInMillis % (60 * 1000)) / 1000D; // Convert remaining milliseconds to seconds
|
||||
final String formattedDuration = String.format("%d minutes, %.0f seconds", minutes, seconds);
|
||||
final String startTimestamp = String.valueOf(currentAttack.duration.getStart() / 1000L);
|
||||
final String endTimestamp = String.valueOf(System.currentTimeMillis() / 1000L);
|
||||
final long blacklisted = Sonar.get().getFallback().getBlacklisted().estimatedSize();
|
||||
final long verified = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
final long failed = Statistics.FAILED_VERIFICATIONS.get();
|
||||
// Calculate during-attack-statistics using their deltas
|
||||
final long totalVerified = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
final long verified = Math.max(totalVerified - currentAttack.successfulVerifications, 0);
|
||||
final long totalFailed = Statistics.FAILED_VERIFICATIONS.get();
|
||||
final long failed = Math.max(totalFailed - currentAttack.failedVerifications, 0);
|
||||
|
||||
// Post webhook to Discord
|
||||
Optional.ofNullable(Sonar.get().getConfig().getDiscordWebhook()).ifPresent(webhook -> webhook.post(() -> {
|
||||
@@ -114,6 +129,7 @@ public final class AttackTracker implements JVMProfiler {
|
||||
.replace("%peak-cpu%", peakCPU)
|
||||
.replace("%peak-memory%", peakMem)
|
||||
.replace("%peak-bps%", peakBPS)
|
||||
.replace("%peak-cps%", peakCPS)
|
||||
.replace("%total-blacklisted%", Sonar.DECIMAL_FORMAT.format(blacklisted))
|
||||
.replace("%total-failed%", Sonar.DECIMAL_FORMAT.format(failed))
|
||||
.replace("%total-success%", Sonar.DECIMAL_FORMAT.format(verified));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -28,8 +28,6 @@ import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@@ -51,18 +49,7 @@ public abstract class Subcommand {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp
|
||||
private static final Pattern IP_PATTERN = Pattern.compile("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$");
|
||||
|
||||
protected static @Nullable InetAddress getInetAddressIfValid(final InvocationSource source, final String rawIP) {
|
||||
// We perform a regex check beforehand,
|
||||
// so we don't have to create a new InetAddress,
|
||||
// therefore, potentially saving some performance.
|
||||
if (!IP_PATTERN.matcher(Objects.requireNonNull(rawIP)).matches()) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIncorrectIpAddress());
|
||||
return null;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawIP);
|
||||
@@ -72,7 +59,7 @@ public abstract class Subcommand {
|
||||
return null;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getUnknownIpAddress());
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIncorrectIpAddress());
|
||||
return null;
|
||||
}
|
||||
return inetAddress;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -112,4 +112,9 @@ public final class SimpleYamlConfig {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getStringList(path);
|
||||
}
|
||||
|
||||
public List<Integer> getIntList(final String path, final List<Integer> def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getIntegerList(path);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -27,12 +27,11 @@ import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.dependencies.Dependency;
|
||||
import xyz.jonesdev.sonar.api.webhook.DiscordWebhook;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.LINE_SEPARATOR;
|
||||
@@ -51,8 +50,6 @@ public final class SonarConfiguration {
|
||||
@Getter
|
||||
private final Verification verification = new Verification();
|
||||
@Getter
|
||||
private final Lockdown lockdown = new Lockdown();
|
||||
@Getter
|
||||
private final Database database = new Database();
|
||||
@Getter
|
||||
private final Webhook webhook = new Webhook();
|
||||
@@ -101,12 +98,53 @@ public final class SonarConfiguration {
|
||||
private Timing timing;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Timing {
|
||||
ALWAYS, DURING_ATTACK, NEVER
|
||||
}
|
||||
|
||||
private boolean checkGravity;
|
||||
private final Map map = new Map();
|
||||
private final Gravity gravity = new Gravity();
|
||||
|
||||
@Getter
|
||||
public static final class Map {
|
||||
private Timing timing;
|
||||
private boolean randomizePositions;
|
||||
private boolean randomizeFontSize;
|
||||
private double distortionsFactorX;
|
||||
private double distortionsFactorY;
|
||||
private int randomLinesAmount;
|
||||
private int randomOvalsAmount;
|
||||
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;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static final class Gravity {
|
||||
private boolean enabled;
|
||||
private Gamemode gamemode;
|
||||
private int maxMovementTicks;
|
||||
private int maxIgnoredTicks;
|
||||
private Component youAreBeingChecked;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Gamemode {
|
||||
SURVIVAL(0),
|
||||
CREATIVE(1),
|
||||
ADVENTURE(2),
|
||||
// Keep this for backwards compatibility
|
||||
SPECTATOR(2);
|
||||
|
||||
private final int id;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean logConnections;
|
||||
private boolean logDuringAttack;
|
||||
private boolean debugXYZPositions;
|
||||
@@ -118,27 +156,13 @@ public final class SonarConfiguration {
|
||||
private String successLog;
|
||||
private String blacklistLog;
|
||||
|
||||
private Gamemode gamemode;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Gamemode {
|
||||
SURVIVAL(0),
|
||||
CREATIVE(1),
|
||||
ADVENTURE(2),
|
||||
SPECTATOR(3);
|
||||
|
||||
private final int id;
|
||||
}
|
||||
|
||||
private int maxBrandLength;
|
||||
private int maxMovementTicks;
|
||||
private int maxIgnoredTicks;
|
||||
private int maxVerifyingPlayers;
|
||||
private int maxLoginPackets;
|
||||
private int maxPing;
|
||||
private int readTimeout;
|
||||
private int reconnectDelay;
|
||||
private final Collection<Integer> whitelistedProtocols = new HashSet<>(0);
|
||||
private final Collection<Integer> blacklistedProtocols = new HashSet<>(0);
|
||||
|
||||
private Component tooManyPlayers;
|
||||
private Component tooFastReconnect;
|
||||
@@ -150,6 +174,8 @@ public final class SonarConfiguration {
|
||||
private Component alreadyVerifying;
|
||||
private Component alreadyQueued;
|
||||
private Component blacklisted;
|
||||
private Component protocolBlacklisted;
|
||||
private Component currentlyPreparing;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -158,7 +184,6 @@ public final class SonarConfiguration {
|
||||
private String incorrectCommandUsage;
|
||||
private String incorrectIpAddress;
|
||||
private String illegalIpAddress;
|
||||
private String unknownIpAddress;
|
||||
private String subCommandNoPerm;
|
||||
private String playersOnly;
|
||||
private String consoleOnly;
|
||||
@@ -218,21 +243,7 @@ public final class SonarConfiguration {
|
||||
private String name;
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Lockdown {
|
||||
@Setter
|
||||
private boolean enabled;
|
||||
private boolean notifyAdmins;
|
||||
private boolean logAttempts;
|
||||
private Component disconnect;
|
||||
private String bypassPermission;
|
||||
private String activated;
|
||||
private String deactivated;
|
||||
private String notification;
|
||||
private String consoleLog;
|
||||
private int maximumAge;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -329,7 +340,8 @@ public final class SonarConfiguration {
|
||||
|
||||
messagesConfig.getYaml().setComment("prefix",
|
||||
"Placeholder for every '%prefix%' in this configuration file");
|
||||
prefix = formatString(messagesConfig.getString("prefix", "<yellow><bold>Sonar<reset> <gray>» <white>"));
|
||||
prefix = formatString(messagesConfig.getString("prefix",
|
||||
"<b><yellow>Sonar</b> <gray>» <white>"));
|
||||
|
||||
messagesConfig.getYaml().setComment("support-url",
|
||||
"Placeholder for every '%support-url%' in this configuration file");
|
||||
@@ -368,26 +380,9 @@ public final class SonarConfiguration {
|
||||
"Password for authenticating with the SQL database");
|
||||
database.password = generalConfig.getString("database.password", "");
|
||||
|
||||
// Lockdown
|
||||
generalConfig.getYaml().setComment("lockdown",
|
||||
"You can lock your server down using '/sonar lockdown' if, for example,"
|
||||
+ LINE_SEPARATOR + "bots are bypassing the verification or any other reason");
|
||||
generalConfig.getYaml().setComment("lockdown.enabled",
|
||||
"Should Sonar prevent all players from joining the server?");
|
||||
lockdown.enabled = generalConfig.getBoolean("lockdown.enabled", false);
|
||||
|
||||
generalConfig.getYaml().setComment("lockdown.log-attempts",
|
||||
"Should Sonar log new login attempts during lockdown?");
|
||||
lockdown.logAttempts = generalConfig.getBoolean("lockdown.log-attempts", true);
|
||||
|
||||
generalConfig.getYaml().setComment("lockdown.notify-admins",
|
||||
"Should Sonar notify admins when they join the server during lockdown?");
|
||||
lockdown.notifyAdmins = generalConfig.getBoolean("lockdown.notify-admins", true);
|
||||
|
||||
generalConfig.getYaml().setComment("lockdown.bypass-permission",
|
||||
"Which permission does a player need in order to bypass the lockdown mode?"
|
||||
+ LINE_SEPARATOR + "Players with this permission will also receive admin notifications when joining");
|
||||
lockdown.bypassPermission = generalConfig.getString("lockdown.bypass-permission", "sonar.lockdown.bypass");
|
||||
generalConfig.getYaml().setComment("database.maximum-age",
|
||||
"How many days should Sonar keep verified players in the database?");
|
||||
database.maximumAge = clamp(generalConfig.getInt("database.maximum-age", 5), 1, 365);
|
||||
|
||||
// Queue
|
||||
generalConfig.getYaml().setComment("queue",
|
||||
@@ -415,17 +410,90 @@ public final class SonarConfiguration {
|
||||
+ LINE_SEPARATOR + "All predicted motions are precalculated in order to save performance");
|
||||
generalConfig.getYaml().setComment("verification.checks.gravity.enabled",
|
||||
"Should Sonar check for valid client gravity? (Recommended)");
|
||||
verification.checkGravity = generalConfig.getBoolean("verification.checks.gravity.enabled", true);
|
||||
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled", true);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.gravity.max-movement-ticks",
|
||||
"Maximum number of ticks the player has to fall in order to be allowed to hit the platform");
|
||||
verification.maxMovementTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-movement-ticks", 8),
|
||||
2, 100);
|
||||
verification.gravity.maxMovementTicks = clamp(generalConfig.getInt(
|
||||
"verification.checks.gravity.max-movement-ticks", 8), 2, 100);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.gravity.max-ignored-ticks",
|
||||
"Maximum number of ignored Y movement changes before a player fails verification");
|
||||
verification.maxIgnoredTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-ignored-ticks", 5), 1,
|
||||
128);
|
||||
verification.gravity.maxIgnoredTicks = clamp(generalConfig.getInt(
|
||||
"verification.checks.gravity.max-ignored-ticks", 5), 1, 128);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.gravity.gamemode",
|
||||
"The gamemode of the player during verification"
|
||||
+ LINE_SEPARATOR + "Possible types: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR"
|
||||
+ LINE_SEPARATOR + "- SURVIVAL: all UI components are visible"
|
||||
+ LINE_SEPARATOR + "- CREATIVE: health and hunger are hidden"
|
||||
+ LINE_SEPARATOR + "- ADVENTURE: all UI components are visible");
|
||||
verification.gravity.gamemode = Verification.Gravity.Gamemode.valueOf(
|
||||
generalConfig.getString("verification.checks.gravity.gamemode",
|
||||
Verification.Gravity.Gamemode.ADVENTURE.name()).toUpperCase());
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha",
|
||||
"Make the player type a code from a virtual map in chat");
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.timing",
|
||||
"When should Sonar make the player solve a captcha?"
|
||||
+ LINE_SEPARATOR + "Possible types: ALWAYS, DURING_ATTACK, NEVER"
|
||||
+ LINE_SEPARATOR + "- ALWAYS: New players will always receive a captcha"
|
||||
+ LINE_SEPARATOR + "- DURING_ATTACK: New players will only receive a captcha during an attack"
|
||||
+ LINE_SEPARATOR + "- NEVER: New players will never receive a captcha (Recommended)");
|
||||
verification.map.timing = Verification.Timing.valueOf(
|
||||
generalConfig.getString("verification.checks.map-captcha.timing",
|
||||
Verification.Timing.NEVER.name()).toUpperCase());
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.random-position",
|
||||
"Should Sonar randomize the X and Y position of the captcha?");
|
||||
verification.map.randomizePositions = generalConfig.getBoolean("verification.checks.map-captcha.random-position",
|
||||
true);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.distortions-factor-x",
|
||||
"How much should Sonar distort characters (factor for randomization)?");
|
||||
verification.map.distortionsFactorX = clamp(generalConfig.getInt(
|
||||
"verification.checks.map-captcha.distortions-factor-x", 50), 0, 100) / 100D;
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.distortions-factor-y",
|
||||
"How much should Sonar distort characters (factor for randomization)?");
|
||||
verification.map.distortionsFactorY = clamp(generalConfig.getInt(
|
||||
"verification.checks.map-captcha.distortions-factor-y", 50), 0, 100) / 100D;
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.random-lines",
|
||||
"How many random lines behind the captcha should Sonar draw?");
|
||||
verification.map.randomLinesAmount = generalConfig.getInt("verification.checks.map-captcha.random-lines", 4);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.random-ovals",
|
||||
"How many random ovals behind the captcha should Sonar draw?");
|
||||
verification.map.randomOvalsAmount = generalConfig.getInt("verification.checks.map-captcha.random-ovals", 1);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.random-font-size",
|
||||
"Should Sonar randomize the size of the font used for rendering the captcha?");
|
||||
verification.map.randomizeFontSize = generalConfig.getBoolean("verification.checks.map-captcha.random-font-size",
|
||||
true);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.precompute",
|
||||
"How many answers should Sonar precompute (prepare)?"
|
||||
+ LINE_SEPARATOR + "This task happens asynchronously in the background;"
|
||||
+ LINE_SEPARATOR + "Players are able to join once one captcha has been prepared");
|
||||
verification.map.precomputeAmount = generalConfig.getInt("verification.checks.map-captcha.precompute", 1000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.max-duration",
|
||||
"How long should Sonar wait until the player fails the captcha?");
|
||||
verification.map.maxDuration = generalConfig.getInt("verification.checks.map-captcha.max-duration", 45000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.max-tries",
|
||||
"How many times must a player fail the captcha before failing the verification?");
|
||||
verification.map.maxTries = generalConfig.getInt("verification.checks.map-captcha.max-tries", 3);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.dictionary",
|
||||
"Characters (letters and numbers) that are allowed to appear in the answer to the captcha");
|
||||
verification.map.dictionary = generalConfig.getString("verification.checks.map-captcha.dictionary", "123456789");
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.fonts",
|
||||
"Which font types should Sonar use for the map captcha codes?");
|
||||
verification.map.fonts = generalConfig.getStringList("verification.checks.map-captcha.fonts",
|
||||
Arrays.asList(Font.DIALOG, Font.DIALOG_INPUT, Font.SERIF, Font.SANS_SERIF));
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.valid-name-regex",
|
||||
"Regex for validating usernames during verification");
|
||||
@@ -454,16 +522,6 @@ public final class SonarConfiguration {
|
||||
"Maximum number of login packets the player has to send in order to be kicked");
|
||||
verification.maxLoginPackets = clamp(generalConfig.getInt("verification.checks.max-login-packets", 256), 128, 8192);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.gamemode",
|
||||
"The gamemode of the player during verification"
|
||||
+ LINE_SEPARATOR + "Possible types: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR"
|
||||
+ LINE_SEPARATOR + "- SURVIVAL: all UI components are visible"
|
||||
+ LINE_SEPARATOR + "- CREATIVE: health and hunger are hidden"
|
||||
+ LINE_SEPARATOR + "- ADVENTURE: all UI components are visible"
|
||||
+ LINE_SEPARATOR + "- SPECTATOR: all UI components are hidden (Recommended)");
|
||||
verification.gamemode = Verification.Gamemode.valueOf(
|
||||
generalConfig.getString("verification.gamemode", Verification.Gamemode.SPECTATOR.name()).toUpperCase());
|
||||
|
||||
generalConfig.getYaml().setComment("verification.log-connections",
|
||||
"Should Sonar log new verification attempts?");
|
||||
verification.logConnections = generalConfig.getBoolean("verification.log-connections", true);
|
||||
@@ -481,16 +539,25 @@ public final class SonarConfiguration {
|
||||
"Amount of time that has to pass before a player times out");
|
||||
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout", 3500), 500, 30000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.max-players",
|
||||
"Maximum number of players verifying at the same time");
|
||||
verification.maxVerifyingPlayers = clamp(generalConfig.getInt("verification.max-players", 1024), 1,
|
||||
Short.MAX_VALUE);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.rejoin-delay",
|
||||
"Minimum number of rejoin delay during verification");
|
||||
verification.reconnectDelay = clamp(generalConfig.getInt("verification.rejoin-delay", 8000), 0, 100000);
|
||||
|
||||
// Webhooks
|
||||
generalConfig.getYaml().setComment("verification.whitelisted-protocols",
|
||||
"List of protocol IDs which are not checked by Sonar (verification bypass)"
|
||||
+ LINE_SEPARATOR + "You can find the full list of all protocol IDs here:"
|
||||
+ LINE_SEPARATOR + "https://wiki.vg/Protocol_version_numbers"
|
||||
+ LINE_SEPARATOR + "For example, Minecraft 1.20 has the ID 763.");
|
||||
verification.whitelistedProtocols.clear();
|
||||
verification.whitelistedProtocols.addAll(generalConfig.getIntList("verification.whitelisted-protocols",
|
||||
new ArrayList<>(0)));
|
||||
|
||||
generalConfig.getYaml().setComment("verification.blacklisted-protocols",
|
||||
"List of protocol IDs which are unable to join the server at all");
|
||||
verification.blacklistedProtocols.clear();
|
||||
verification.blacklistedProtocols.addAll(generalConfig.getIntList("verification.blacklisted-protocols",
|
||||
new ArrayList<>(0)));
|
||||
|
||||
generalConfig.getYaml().setComment("webhook",
|
||||
"Bot attack notifications can also be sent to your Discord server using webhooks");
|
||||
generalConfig.getYaml().setComment("webhook.url",
|
||||
@@ -575,8 +642,8 @@ public final class SonarConfiguration {
|
||||
"Placeholder for every '%header%' in this configuration file");
|
||||
header = fromList(messagesConfig.getStringList("header",
|
||||
Arrays.asList(
|
||||
"<yellow><bold>Sonar<reset>",
|
||||
"<reset>"
|
||||
"<b><yellow>Sonar</b><reset>",
|
||||
""
|
||||
)));
|
||||
|
||||
messagesConfig.getYaml().setComment("footer",
|
||||
@@ -605,7 +672,7 @@ public final class SonarConfiguration {
|
||||
"%prefix%<red>Usage: /sonar %usage%"));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.invalid-ip-address",
|
||||
"Message that is shown when someone provides an invalid IP address (Invalid characters)");
|
||||
"Message that is shown when someone provides an invalid IP address (Invalid format)");
|
||||
commands.incorrectIpAddress = formatString(messagesConfig.getString("commands.invalid-ip-address",
|
||||
"%prefix%The IP address you provided seems to be invalid."));
|
||||
|
||||
@@ -614,11 +681,6 @@ public final class SonarConfiguration {
|
||||
commands.illegalIpAddress = formatString(messagesConfig.getString("commands.illegal-ip-address",
|
||||
"%prefix%The IP address you provided seems to be either a local or loopback IP."));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.unknown-ip-address",
|
||||
"Message that is shown when someone provides an unknown IP address (Unknown Host)");
|
||||
commands.unknownIpAddress = formatString(messagesConfig.getString("commands.unknown-ip-address",
|
||||
"%prefix%The IP address you provided seems to be unknown."));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.player-only",
|
||||
"Message that is shown when the console runs a command that is player-only");
|
||||
commands.playersOnly = formatString(messagesConfig.getString("commands.player-only",
|
||||
@@ -641,44 +703,21 @@ public final class SonarConfiguration {
|
||||
commands.subCommandNoPerm = formatString(messagesConfig.getString("commands.subcommand-no-permission",
|
||||
"%prefix%<red>You do not have permission to execute this subcommand. <gray>(%permission%)"));
|
||||
|
||||
messagesConfig.getYaml().setComment("lockdown",
|
||||
"Translations for '/sonar lockdown'");
|
||||
messagesConfig.getYaml().setComment("lockdown.notification",
|
||||
"Message that is shown when an admin joins the server during lockdown");
|
||||
lockdown.notification = formatString(messagesConfig.getString("lockdown.notification",
|
||||
"%prefix%<green>Hey, the server is currently in lockdown mode. If you want to disable the lockdown mode,"
|
||||
+ " type <white>/sonar lockdown<green>."
|
||||
));
|
||||
|
||||
messagesConfig.getYaml().setComment("lockdown.console-log",
|
||||
"Message that is shown to console when a normal player tries joining the server during lockdown");
|
||||
lockdown.consoleLog = messagesConfig.getString("lockdown.console-log",
|
||||
"%player% (%ip%, %protocol%) tried to join during lockdown mode.");
|
||||
|
||||
messagesConfig.getYaml().setComment("lockdown.disconnect-message",
|
||||
"Message that is shown to a normal player when they try joining the server during lockdown");
|
||||
lockdown.disconnect = deserialize(fromList(messagesConfig.getStringList("lockdown.disconnect-message",
|
||||
Arrays.asList(
|
||||
"%header%",
|
||||
"<red>The server is currently locked down, please try again later.",
|
||||
"%footer%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.main",
|
||||
"Translations for '/sonar'");
|
||||
messagesConfig.getYaml().setComment("commands.main.header",
|
||||
"Informational message that is shown above everything when running the main command");
|
||||
commands.helpHeader = messagesConfig.getStringList("commands.main.header",
|
||||
Arrays.asList(
|
||||
"<yellow>Running Sonar %version% on %platform%.",
|
||||
"<yellow>(C) %copyright_year% Jones Development and Sonar Contributors",
|
||||
"<green><click:open_url:'https://github.com/jonesdevelopment/sonar'>https://github.com/jonesdevelopment/sonar",
|
||||
"<yellow>Running <b>Sonar</b> %version% on %platform%.",
|
||||
"<gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year% Jones Development and" +
|
||||
" Sonar Contributors</click>",
|
||||
"",
|
||||
"<yellow>Need help or have any questions?",
|
||||
"<yellow><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click to open Discord)'>Open a " +
|
||||
"ticket on the Discord </hover></click></yellow><yellow><click:open_url:'https://github" +
|
||||
".com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click to open GitHub)'>or open a new issue on " +
|
||||
"GitHub.",
|
||||
"<gray>Need help or have any questions?",
|
||||
"<white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click to open Discord)'>Open a " +
|
||||
"ticket on the Discord </hover></click><click:open_url:'https://github" +
|
||||
".com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click to open GitHub)'>or open a new issue on" +
|
||||
" GitHub.",
|
||||
""
|
||||
));
|
||||
messagesConfig.getYaml().setComment("commands.main.subcommands",
|
||||
@@ -686,8 +725,8 @@ public final class SonarConfiguration {
|
||||
commands.helpSubcommands = formatString(messagesConfig.getString("commands.main.subcommands",
|
||||
"<click:suggest_command:'/sonar %subcommand% '><hover:show_text:'<gray>Only players: " +
|
||||
"</gray>%only_players%<br><gray>Require console: </gray>%require_console%<br><gray>Permission: " +
|
||||
"</gray><white>%permission%<br><gray>Aliases: </gray>%aliases%'><gray> ▪ </gray><green>/sonar " +
|
||||
"%subcommand%</green><gray> - </gray><white>%description%"));
|
||||
"</gray><white>%permission%<br><gray>Aliases: </gray>%aliases%'><dark_aqua> ▪ <gray>/sonar " +
|
||||
"%subcommand% <white>%description%"));
|
||||
|
||||
SonarCommand.prepareCachedMessages();
|
||||
|
||||
@@ -703,18 +742,6 @@ public final class SonarConfiguration {
|
||||
commands.reloaded = formatString(messagesConfig.getString("commands.reload.finish",
|
||||
"%prefix%<green>Successfully reloaded <gray>(%taken%ms)"));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.lockdown",
|
||||
"Translations for '/sonar lockdown'");
|
||||
messagesConfig.getYaml().setComment("commands.lockdown.enabled",
|
||||
"Message that is shown when a player enables server lockdown");
|
||||
lockdown.activated = formatString(messagesConfig.getString("commands.lockdown.enabled",
|
||||
"%prefix%The server is now in lockdown mode."));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.lockdown.disabled",
|
||||
"Message that is shown when a player disables server lockdown");
|
||||
lockdown.deactivated = formatString(messagesConfig.getString("commands.lockdown.disabled",
|
||||
"%prefix%The server is no longer in lockdown mode."));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.verbose",
|
||||
"Translations for '/sonar verbose'");
|
||||
messagesConfig.getYaml().setComment("commands.verbose.subscribed",
|
||||
@@ -807,7 +834,7 @@ public final class SonarConfiguration {
|
||||
messagesConfig.getYaml().setComment("commands.statistics.header",
|
||||
"Informational message that is shown above everything when viewing the statistics");
|
||||
commands.statisticsHeader = formatString(messagesConfig.getString("commands.statistics.header",
|
||||
"%prefix%<yellow>Showing %type% statistics for this session:"));
|
||||
"%prefix%Showing %type% statistics for this session:"));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.statistics.unknown-type",
|
||||
"Message that is shown when a player tries viewing an unknown statistic");
|
||||
@@ -818,45 +845,46 @@ public final class SonarConfiguration {
|
||||
"Format of the general statistics message");
|
||||
commands.generalStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.general",
|
||||
Arrays.asList(
|
||||
" <gray>▪ <green>Verified IP addresses: <white>%verified%",
|
||||
" <gray>▪ <green>Verifying IP addresses: <white>%verifying%",
|
||||
" <gray>▪ <green>Blacklisted IP addresses: <white>%blacklisted%",
|
||||
" <gray>▪ <green>Currently queued logins: <white>%queued%",
|
||||
" <gray>▪ <green>Total non-unique joins: <white>%total_joins%",
|
||||
" <gray>▪ <green>Total verification attempts: <white>%total_attempts%",
|
||||
" <gray>▪ <green>Total failed verifications: <white>%total_failed%"
|
||||
" <dark_aqua>▪ <gray>Verified IP addresses: <white>%verified%",
|
||||
" <dark_aqua>▪ <gray>Verifying IP addresses: <white>%verifying%",
|
||||
" <dark_aqua>▪ <gray>Blacklisted IP addresses: <white>%blacklisted%",
|
||||
" <dark_aqua>▪ <gray>Currently queued logins: <white>%queued%",
|
||||
" <dark_aqua>▪ <gray>Total non-unique joins: <white>%total_joins%",
|
||||
" <dark_aqua>▪ <gray>Total verification attempts: <white>%total_attempts%",
|
||||
" <dark_aqua>▪ <gray>Total failed verifications: <white>%total_failed%",
|
||||
" <dark_aqua>▪ <gray>Server uptime: <white>%uptime%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.statistics.cpu",
|
||||
"Format of the CPU statistics message");
|
||||
commands.cpuStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.cpu",
|
||||
Arrays.asList(
|
||||
" <gray>▪ <green>Process CPU usage right now: <white>%process_cpu%%",
|
||||
" <gray>▪ <green>System CPU usage right now: <white>%system_cpu%%",
|
||||
" <gray>▪ <green>Per-core process CPU usage: <white>%average_process_cpu%%",
|
||||
" <gray>▪ <green>Per-core system CPU usage: <white>%average_system_cpu%%",
|
||||
" <gray>▪ <green>General system load average: <white>%load_average%%",
|
||||
" <gray>▪ <green>Total amount of virtual cpus: <white>%virtual_cores%"
|
||||
" <dark_aqua>▪ <gray>Process CPU usage right now: <white>%process_cpu%%",
|
||||
" <dark_aqua>▪ <gray>System CPU usage right now: <white>%system_cpu%%",
|
||||
" <dark_aqua>▪ <gray>Per-core process CPU usage: <white>%average_process_cpu%%",
|
||||
" <dark_aqua>▪ <gray>Per-core system CPU usage: <white>%average_system_cpu%%",
|
||||
" <dark_aqua>▪ <gray>General system load average: <white>%load_average%%",
|
||||
" <dark_aqua>▪ <gray>Total amount of virtual cpus: <white>%virtual_cores%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.statistics.memory",
|
||||
"Format of the memory statistics message");
|
||||
commands.memoryStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.memory",
|
||||
Arrays.asList(
|
||||
" <gray>▪ <green>Total free memory: <white>%free_memory%",
|
||||
" <gray>▪ <green>Total used memory: <white>%used_memory%",
|
||||
" <gray>▪ <green>Total maximum memory: <white>%max_memory%",
|
||||
" <gray>▪ <green>Total allocated memory: <white>%total_memory%"
|
||||
" <dark_aqua>▪ <gray>Total free memory: <white>%free_memory%",
|
||||
" <dark_aqua>▪ <gray>Total used memory: <white>%used_memory%",
|
||||
" <dark_aqua>▪ <gray>Total maximum memory: <white>%max_memory%",
|
||||
" <dark_aqua>▪ <gray>Total allocated memory: <white>%total_memory%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("commands.statistics.network",
|
||||
"Format of the network statistics message");
|
||||
commands.networkStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.network",
|
||||
Arrays.asList(
|
||||
" <gray>▪ <green>Current incoming used bandwidth: <white>%incoming%",
|
||||
" <gray>▪ <green>Current outgoing used bandwidth: <white>%outgoing%",
|
||||
" <gray>▪ <green>Total incoming used bandwidth: <white>%ttl_incoming%",
|
||||
" <gray>▪ <green>Total outgoing used bandwidth: <white>%ttl_outgoing%"
|
||||
" <dark_aqua>▪ <gray>Current incoming used bandwidth: <white>%incoming%",
|
||||
" <dark_aqua>▪ <gray>Current outgoing used bandwidth: <white>%outgoing%",
|
||||
" <dark_aqua>▪ <gray>Total incoming used bandwidth: <white>%ttl_incoming%",
|
||||
" <dark_aqua>▪ <gray>Total outgoing used bandwidth: <white>%ttl_outgoing%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification",
|
||||
@@ -883,6 +911,36 @@ public final class SonarConfiguration {
|
||||
verification.successLog = formatString(messagesConfig.getString("verification.logs.successful",
|
||||
"%name% has been verified successfully (%time%s!)."));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.welcome",
|
||||
"Message that is shown to the player when they are being checked for valid gravity");
|
||||
verification.gravity.youAreBeingChecked = deserialize(formatString(messagesConfig.getString("verification.welcome",
|
||||
"%prefix%<gray>Please wait a moment for the verification to finish...")));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.captcha.enter-code",
|
||||
"Message that is shown to the player when they have to enter the answer to the captcha");
|
||||
verification.map.enterCode = deserialize(formatString(messagesConfig.getString("verification.captcha.enter-code",
|
||||
"%prefix%<green>Please enter the code in chat that is displayed on the map.")));
|
||||
messagesConfig.getYaml().setComment("verification.captcha.action-bar",
|
||||
"Timer that is shown to the player when they have to enter the answer to the captcha"
|
||||
+ LINE_SEPARATOR + "(Set this to '' to disable the action bar message)");
|
||||
verification.map.enterCodeActionBar = formatString(messagesConfig.getString("verification.captcha.action-bar",
|
||||
"%prefix%<green>You have %time-left% seconds left to enter the code in chat"));
|
||||
messagesConfig.getYaml().setComment("verification.captcha.incorrect",
|
||||
"Message that is shown to the player when they enter the wrong answer in chat");
|
||||
verification.map.failedCaptcha = deserialize(formatString(messagesConfig.getString("verification.captcha.incorrect",
|
||||
"%prefix%<red>You have entered the wrong code. Please try again.")));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.currently-preparing",
|
||||
"Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet");
|
||||
verification.currentlyPreparing = deserialize(fromList(messagesConfig.getStringList(
|
||||
"verification.currently-preparing",
|
||||
Arrays.asList(
|
||||
"%header%",
|
||||
"<yellow>Your anti-bot data has not been prepared yet.",
|
||||
"<gray>Please wait a few seconds before trying to verify again.",
|
||||
"%footer%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.too-many-players",
|
||||
"Disconnect message that is shown when too many players are verifying at the same time");
|
||||
verification.tooManyPlayers = deserialize(fromList(messagesConfig.getStringList("verification.too-many-players",
|
||||
@@ -952,6 +1010,17 @@ public final class SonarConfiguration {
|
||||
"%footer%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.blacklisted-protocol",
|
||||
"Disconnect message that is shown when someone joins with a blacklisted version");
|
||||
verification.protocolBlacklisted = deserialize(fromList(messagesConfig.getStringList("verification" +
|
||||
".blacklisted-protocol",
|
||||
Arrays.asList(
|
||||
"%header%",
|
||||
"<red>You are using a version that is not allowed on our server.",
|
||||
"<gold>Need help logging in? <gray>%support-url%",
|
||||
"%footer%"
|
||||
))));
|
||||
|
||||
messagesConfig.getYaml().setComment("verification.already-online",
|
||||
"Disconnect message that is shown when someone tries verifying with an account that is online");
|
||||
verification.alreadyConnected = deserialize(fromList(messagesConfig.getStringList("verification.already-online",
|
||||
@@ -996,6 +1065,7 @@ public final class SonarConfiguration {
|
||||
+ LINE_SEPARATOR + "- '%verify-success%' Number of verified IP addresses"
|
||||
+ LINE_SEPARATOR + "- '%verify-failed%' Number of failed verifications"
|
||||
+ LINE_SEPARATOR + "- '%logins-per-second%' Number of logins per second"
|
||||
+ LINE_SEPARATOR + "- '%connections-per-second%' Number of connections per second"
|
||||
+ LINE_SEPARATOR + "- '%attack-duration%' Duration of the current attack"
|
||||
+ LINE_SEPARATOR + "- '%incoming-traffic%' Incoming bandwidth usage per second"
|
||||
+ LINE_SEPARATOR + "- '%outgoing-traffic%' Outgoing bandwidth usage per second"
|
||||
@@ -1009,24 +1079,22 @@ public final class SonarConfiguration {
|
||||
+ LINE_SEPARATOR + "Translations for Sonar's normal verbose output");
|
||||
verbose.actionBarLayout = formatString(messagesConfig.getString("verbose.layout.normal",
|
||||
String.join(" <dark_aqua>╺ ", Arrays.asList(
|
||||
"%prefix%<gray>Queued <white>%queued%",
|
||||
"<gray>Verifying <white>%verifying%",
|
||||
"<gray>Blacklisted <white>%blacklisted%" +
|
||||
" <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s" +
|
||||
" <green><bold>%animation%<reset>"
|
||||
))));
|
||||
"%prefix%<gray>CPS <white>%connections-per-second%",
|
||||
"<gray>Logins/s <white>%logins-per-second%",
|
||||
"<gray>Verifying <white>%verifying%"
|
||||
+ " <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s"
|
||||
+ " <green><bold>%animation%<reset>"))));
|
||||
messagesConfig.getYaml().setComment("verbose.layout.attack",
|
||||
"Translations for Sonar's verbose output during an active attack");
|
||||
verbose.actionBarLayoutDuringAttack = formatString(messagesConfig.getString("verbose.layout.attack",
|
||||
String.join(" <dark_aqua>╺ ", Arrays.asList(
|
||||
"%prefix%<gray>BPS <white>%logins-per-second%",
|
||||
"<gray>Queued <white>%queued%",
|
||||
"%prefix%<gray>CPS <white>%connections-per-second%",
|
||||
"<gray>Logins/s <white>%logins-per-second%",
|
||||
"<gray>Queue <white>%queued%",
|
||||
"<gray>Verifying <white>%verifying%",
|
||||
"<gray>Blacklisted <white>%blacklisted%",
|
||||
"<gray>Duration <white>%attack-duration%" +
|
||||
" <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s" +
|
||||
" <green><bold>%animation%<reset>"
|
||||
))));
|
||||
"<gray>Duration <white>%attack-duration%"
|
||||
+ " <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s"
|
||||
+ " <green><bold>%animation%<reset>"))));
|
||||
messagesConfig.getYaml().setComment("verbose.animation", "Animation for the action bar"
|
||||
+ LINE_SEPARATOR + "Alternatives:"
|
||||
+ LINE_SEPARATOR + "- ▙, ▛, ▜, ▟"
|
||||
|
||||
+43
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -25,6 +25,7 @@ import com.j256.ormlite.table.TableUtils;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.dependencies.DependencyLoader;
|
||||
@@ -32,6 +33,9 @@ import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -72,13 +76,49 @@ public final class VerifiedPlayerController {
|
||||
dao = DaoManager.createDao(connectionSource, VerifiedPlayer.class);
|
||||
queryBuilder = dao.queryBuilder();
|
||||
|
||||
dao.queryForAll().forEach(this::_add);
|
||||
// Make sure to run the clean task and the caching task in the same thread
|
||||
// https://github.com/jonesdevelopment/sonar/issues/150
|
||||
DB_UPDATE_SERVICE.execute(() -> {
|
||||
// Make sure to clear all outdated entries first
|
||||
clearOld(Sonar.get().getConfig().getDatabase().getMaximumAge());
|
||||
// Add all entries from the database to the cache
|
||||
try {
|
||||
dao.queryForAll().forEach(this::_add);
|
||||
} catch (SQLException exception) {
|
||||
Sonar.get().getLogger().error("Could not cache entries: {}", exception);
|
||||
}
|
||||
});
|
||||
} catch (Throwable throwable) {
|
||||
Sonar.get().getLogger().error("Error setting up database connection: {}", throwable);
|
||||
Sonar.get().getLogger().error("Error setting up database: {}", throwable);
|
||||
throwable.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all old entries using the given timestamp.
|
||||
*/
|
||||
private void clearOld(final @Range(from = 1, to = 365) int maximumAge) {
|
||||
try {
|
||||
final long timestamp = Instant.now()
|
||||
.minus(maximumAge, ChronoUnit.DAYS)
|
||||
.getEpochSecond() * 1000L; // convert to ms
|
||||
|
||||
final List<VerifiedPlayer> oldEntries = queryBuilder.where()
|
||||
.lt("timestamp", new Timestamp(timestamp))
|
||||
.query();
|
||||
|
||||
if (oldEntries != null) {
|
||||
for (final VerifiedPlayer player : oldEntries) {
|
||||
dao.delete(player);
|
||||
}
|
||||
Sonar.get().getLogger().info("Removed {} database entries older than {} days.",
|
||||
oldEntries.size(), maximumAge);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
Sonar.get().getLogger().error("Error trying to clear old entries: {}", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* First, remove the player from the local cache and then,
|
||||
* secondly, asynchronously add the player to the database.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -31,5 +31,5 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
@RequiredArgsConstructor
|
||||
public final class UserBlacklistedEvent implements SonarEvent {
|
||||
@ApiStatus.Experimental
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackUser<?> user;
|
||||
}
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -30,11 +30,11 @@ import java.util.Optional;
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public final class UserVerifyFailedEvent implements SonarEvent {
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackUser<?> user;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<String> reason;
|
||||
|
||||
public UserVerifyFailedEvent(final FallbackUser<?, ?> user, final @Nullable String reason) {
|
||||
public UserVerifyFailedEvent(final FallbackUser<?> user, final @Nullable String reason) {
|
||||
this.user = user;
|
||||
this.reason = Optional.ofNullable(reason);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -30,5 +30,5 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyJoinEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackUser<?> user;
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -33,6 +33,6 @@ import java.util.UUID;
|
||||
public final class UserVerifySuccessEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final UUID offlineUuid;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackUser<?> user;
|
||||
private final long timeTakenToVerify;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -20,7 +20,6 @@ package xyz.jonesdev.sonar.api.fallback;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.val;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
@@ -40,11 +39,10 @@ public final class Fallback {
|
||||
public static final Fallback INSTANCE = new Fallback();
|
||||
private static final @NotNull Sonar SONAR = Objects.requireNonNull(Sonar.get());
|
||||
|
||||
private final Map<String, InetAddress> connected = new ConcurrentHashMap<>();
|
||||
private final Map<String, InetAddress> connected = new ConcurrentHashMap<>(64, 0.75f);
|
||||
// Only block the player for a few minutes to avoid issues
|
||||
private final ExpiringCache<String> blacklisted = Cappuccino.buildExpiring(
|
||||
10L, TimeUnit.MINUTES, 5000L
|
||||
);
|
||||
private final ExpiringCache<InetAddress> blacklisted = Cappuccino.buildExpiring(
|
||||
10L, TimeUnit.MINUTES, 5000L);
|
||||
private final @NotNull FallbackQueue queue = FallbackQueue.INSTANCE;
|
||||
private final @NotNull FallbackRatelimiter ratelimiter = FallbackRatelimiter.INSTANCE;
|
||||
|
||||
@@ -68,7 +66,14 @@ public final class Fallback {
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean shouldVerifyNewPlayers() {
|
||||
val timing = Sonar.get().getConfig().getVerification().getTiming();
|
||||
return performTimingCheck(Sonar.get().getConfig().getVerification().getTiming());
|
||||
}
|
||||
|
||||
public boolean shouldDoMapCaptcha() {
|
||||
return performTimingCheck(Sonar.get().getConfig().getVerification().getMap().getTiming());
|
||||
}
|
||||
|
||||
private boolean performTimingCheck(final SonarConfiguration.Verification.Timing timing) {
|
||||
return timing == SonarConfiguration.Verification.Timing.ALWAYS
|
||||
|| (timing == SonarConfiguration.Verification.Timing.DURING_ATTACK
|
||||
&& Sonar.get().getAttackTracker().isCurrentlyUnderAttack());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -69,5 +69,7 @@ public final class FallbackQueue {
|
||||
|
||||
// Run the attack check task
|
||||
Sonar.get().getAttackTracker().checkIfUnderAttack();
|
||||
// Clean up the cache of rate-limited IPs
|
||||
Sonar.get().getFallback().getRatelimiter().cleanUpCache();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -31,6 +31,15 @@ public final class FallbackRatelimiter {
|
||||
@Setter
|
||||
private ExpiringCache<InetAddress> expiringCache;
|
||||
|
||||
/**
|
||||
* We don't want to clean up the cache in the
|
||||
* {@link #attempt(InetAddress)} method because
|
||||
* it would take up too many resources.
|
||||
*/
|
||||
public void cleanUpCache() {
|
||||
expiringCache.cleanUp(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player has tried verifying too fast
|
||||
*
|
||||
@@ -39,11 +48,9 @@ public final class FallbackRatelimiter {
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean attempt(final @NotNull InetAddress inetAddress) {
|
||||
// Clean up the cache
|
||||
expiringCache.cleanUp(false);
|
||||
|
||||
// Cache the IP address if it's not already cached
|
||||
if (!expiringCache.has(inetAddress)) {
|
||||
// Cache the IP address
|
||||
expiringCache.put(inetAddress);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -33,12 +33,10 @@ import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface FallbackUser<X, Y> {
|
||||
public interface FallbackUser<T> {
|
||||
@NotNull Fallback getFallback();
|
||||
|
||||
@NotNull X getPlayer();
|
||||
|
||||
@NotNull Y getConnection();
|
||||
@NotNull T getConnection();
|
||||
|
||||
@NotNull Channel getChannel();
|
||||
|
||||
@@ -115,7 +113,7 @@ public interface FallbackUser<X, Y> {
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
|
||||
getFallback().getBlacklisted().put(getInetAddress().toString());
|
||||
getFallback().getBlacklisted().put(getInetAddress());
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN(-1),
|
||||
@@ -64,9 +65,9 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_19_3(761),
|
||||
MINECRAFT_1_19_4(762),
|
||||
MINECRAFT_1_20(763),
|
||||
MINECRAFT_1_20_2(764);
|
||||
MINECRAFT_1_20_2(764),
|
||||
MINECRAFT_1_20_3(765);
|
||||
|
||||
@Getter
|
||||
private final int protocol;
|
||||
|
||||
public static final ProtocolVersion MINIMUM_VERSION = MINECRAFT_1_7_2;
|
||||
@@ -95,7 +96,7 @@ public enum ProtocolVersion {
|
||||
}
|
||||
|
||||
SUPPORTED_VERSIONS = Collections.unmodifiableSet(versions);
|
||||
LATEST_VERSION = new ArrayList<>(versions).get(versions.size() - 1);
|
||||
LATEST_VERSION = (ProtocolVersion) versions.toArray()[versions.size() - 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.statistics;
|
||||
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface Counters {
|
||||
ExpiringCache<Long> LOGINS_PER_SECOND = Cappuccino.buildExpiring(1L, TimeUnit.SECONDS);
|
||||
ExpiringCache<Long> CONNECTIONS_PER_SECOND = Cappuccino.buildExpiring(1L, TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -23,11 +23,10 @@ import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.attack.AttackTracker;
|
||||
import xyz.jonesdev.sonar.api.profiler.JVMProfiler;
|
||||
import xyz.jonesdev.sonar.api.statistics.Counters;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
@@ -35,24 +34,24 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter.INCOMING;
|
||||
import static xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter.OUTGOING;
|
||||
|
||||
@Getter
|
||||
public final class Verbose implements JVMProfiler {
|
||||
public final class Verbose implements JVMProfiler, Counters {
|
||||
private final @NotNull Collection<String> subscribers = new Vector<>(0);
|
||||
private final @NotNull Map<String, Audience> audiences = new ConcurrentHashMap<>();
|
||||
private int animationIndex;
|
||||
private final ExpiringCache<Long> loginsPerSecond = Cappuccino.buildExpiring(1L, TimeUnit.SECONDS);
|
||||
|
||||
// Run action bar verbose
|
||||
public void update() {
|
||||
// Clean up all blacklisted IPs
|
||||
Sonar.get().getFallback().getBlacklisted().cleanUp(false);
|
||||
loginsPerSecond.cleanUp(false);
|
||||
// Clean up all counters
|
||||
LOGINS_PER_SECOND.cleanUp(false);
|
||||
CONNECTIONS_PER_SECOND.cleanUp(false);
|
||||
|
||||
// Don't prepare component if there are no subscribers
|
||||
if (subscribers.isEmpty()) return;
|
||||
@@ -78,7 +77,8 @@ public final class Verbose implements JVMProfiler {
|
||||
.replace("%blacklisted%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklisted().estimatedSize()))
|
||||
.replace("%total-joins%", DECIMAL_FORMAT.format(Statistics.TOTAL_TRAFFIC.get()))
|
||||
.replace("%logins-per-second%", DECIMAL_FORMAT.format(loginsPerSecond.estimatedSize()))
|
||||
.replace("%logins-per-second%", DECIMAL_FORMAT.format(LOGINS_PER_SECOND.estimatedSize()))
|
||||
.replace("%connections-per-second%", DECIMAL_FORMAT.format(CONNECTIONS_PER_SECOND.estimatedSize()))
|
||||
.replace("%verify-total%", DECIMAL_FORMAT.format(Statistics.REAL_TRAFFIC.get()))
|
||||
.replace("%verify-success%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().estimatedSize()))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -29,13 +29,13 @@ import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
|
||||
public final class AudienceListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PlayerJoinEvent event) {
|
||||
final Audience audience = SonarBukkit.INSTANCE.getBukkitAudiences().player(event.getPlayer());
|
||||
Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PlayerQuitEvent event) {
|
||||
Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName());
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -29,13 +29,13 @@ import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
|
||||
public final class AudienceListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PostLoginEvent event) {
|
||||
final Audience audience = SonarBungee.INSTANCE.getBungeeAudiences().player(event.getPlayer());
|
||||
Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PlayerDisconnectEvent event) {
|
||||
Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName());
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -32,7 +32,6 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"redundantSuppression"})
|
||||
public void execute(final @NotNull CommandSender sender, final String[] args) {
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new BungeeInvocationSource(sender);
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -18,12 +18,14 @@
|
||||
package xyz.jonesdev.sonar.bungee.fallback;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.haproxy.HAProxyMessage;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.connection.DownstreamBridge;
|
||||
import net.md_5.bungee.connection.UpstreamBridge;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.netty.HandlerBoss;
|
||||
import net.md_5.bungee.netty.PacketHandler;
|
||||
import net.md_5.bungee.protocol.PacketWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
@@ -60,6 +62,13 @@ public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
if (msg instanceof HAProxyMessage || msg instanceof PacketWrapper) {
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx, final Throwable throwable) throws Exception {
|
||||
if (ctx.channel().isActive()) {
|
||||
|
||||
+63
-42
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -20,6 +20,7 @@ package xyz.jonesdev.sonar.bungee.fallback;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
@@ -32,6 +33,7 @@ import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.PlayerPublicKey;
|
||||
import net.md_5.bungee.protocol.packet.Handshake;
|
||||
import net.md_5.bungee.protocol.packet.Kick;
|
||||
import net.md_5.bungee.protocol.packet.LoginRequest;
|
||||
import net.md_5.bungee.protocol.packet.StatusRequest;
|
||||
@@ -42,6 +44,7 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Counters;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackTimeoutHandler;
|
||||
@@ -74,7 +77,6 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
this.bungee = bungee;
|
||||
}
|
||||
|
||||
private static final @NotNull Fallback FALLBACK = Objects.requireNonNull(Sonar.get().getFallback());
|
||||
@Getter
|
||||
private ChannelWrapper channelWrapper;
|
||||
private final @NotNull BungeeCord bungee;
|
||||
@@ -84,17 +86,30 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
private boolean receivedLoginPacket;
|
||||
private boolean receivedStatusPacket;
|
||||
|
||||
private static final @NotNull Fallback FALLBACK = Objects.requireNonNull(Sonar.get().getFallback());
|
||||
|
||||
@Override
|
||||
public void connected(final ChannelWrapper channelWrapper) throws Exception {
|
||||
this.channelWrapper = channelWrapper;
|
||||
super.connected(channelWrapper);
|
||||
// Increase connections per second for the action bar verbose
|
||||
Counters.CONNECTIONS_PER_SECOND.put(System.nanoTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull Handshake handshake) throws Exception {
|
||||
// Check if the host is invalid
|
||||
if (handshake.getHost().isEmpty()) {
|
||||
throw new CorruptedFrameException("Hostname is empty");
|
||||
}
|
||||
super.handle(handshake);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final StatusRequest statusRequest) throws Exception {
|
||||
// Fix status packet spam exploit
|
||||
if (receivedStatusPacket) {
|
||||
throw new ConditionFailedException("Duplicate status packet");
|
||||
throw new CorruptedFrameException("Duplicate status packet");
|
||||
}
|
||||
receivedStatusPacket = true;
|
||||
// Run the rest of the method asynchronously
|
||||
@@ -102,7 +117,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
// The channel always stays open because the client sends
|
||||
// a StatusRequest and a Ping packet after one another
|
||||
if (!isConnected()) {
|
||||
throw new ConditionFailedException("Not connected anymore");
|
||||
throw new CorruptedFrameException("Not connected anymore");
|
||||
}
|
||||
try {
|
||||
super.handle(statusRequest);
|
||||
@@ -115,46 +130,45 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
@Override
|
||||
public void handle(final LoginRequest loginRequest) throws Exception {
|
||||
// Increase joins per second for the action bar verbose
|
||||
Sonar.get().getVerboseHandler().getLoginsPerSecond().put(System.nanoTime());
|
||||
Counters.LOGINS_PER_SECOND.put(System.nanoTime());
|
||||
|
||||
// Fix login packet spam exploit
|
||||
if (receivedLoginPacket || user != null) {
|
||||
throw new ConditionFailedException("Duplicate login packet");
|
||||
throw new CorruptedFrameException("Duplicate login packet");
|
||||
}
|
||||
receivedLoginPacket = true;
|
||||
// Cache protocol version so other handlers don't throw NPEs
|
||||
protocolVersion = ProtocolVersion.fromId(getHandshake().getProtocolVersion());
|
||||
final int protocolId = getHandshake().getProtocolVersion();
|
||||
protocolVersion = ProtocolVersion.fromId(protocolId);
|
||||
final Channel channel = channelWrapper.getHandle();
|
||||
|
||||
// Run in the channel's event loop
|
||||
channel.eventLoop().execute(() -> {
|
||||
|
||||
// Do not continue if the connection is closed or marked as disconnected
|
||||
if (channelWrapper.isClosed() || channelWrapper.isClosing()) return;
|
||||
|
||||
try {
|
||||
// Hook the custom traffic pipeline, so we can count the incoming and outgoing traffic
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
TrafficChannelHooker.hook(pipeline, PACKET_DECODER, PACKET_ENCODER);
|
||||
|
||||
// Increase total traffic statistic
|
||||
Statistics.TOTAL_TRAFFIC.increment();
|
||||
|
||||
final InetAddress inetAddress = getAddress().getAddress();
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklisted().has(inetAddress)) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getBlacklisted()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the verification is enabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
super.handle(loginRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress = getAddress().getAddress();
|
||||
val uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + loginRequest.getData()).getBytes(StandardCharsets.UTF_8));
|
||||
// Check if the player is already verified
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, uuid)) {
|
||||
super.handle(loginRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklisted().has(inetAddress.toString())) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getBlacklisted()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections
|
||||
if (isGeyserConnection(channel)) {
|
||||
FALLBACK.getLogger().info("Skipping Geyser player: {}{}",
|
||||
@@ -163,10 +177,29 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolId)) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getProtocolBlacklisted()));
|
||||
return;
|
||||
}
|
||||
|
||||
val uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + loginRequest.getData()).getBytes(StandardCharsets.UTF_8));
|
||||
// Check if the player is already verified
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, uuid)) {
|
||||
super.handle(loginRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is allowed to bypass verification
|
||||
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols().contains(protocolId)) {
|
||||
super.handle(loginRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create wrapped Fallback user
|
||||
user = new FallbackUserWrapper(
|
||||
FALLBACK, channelWrapper, this,
|
||||
channel, channel.pipeline(), inetAddress, protocolVersion
|
||||
FALLBACK, this, channel, channel.pipeline(),
|
||||
inetAddress, protocolVersion
|
||||
);
|
||||
|
||||
// Perform default BungeeCord checks
|
||||
@@ -202,13 +235,6 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot allow too many players on our Fallback server
|
||||
// There's technically no reason for limiting this, but we'll better stay safe.
|
||||
if (FALLBACK.getConnected().size() > Sonar.get().getConfig().getVerification().getMaxVerifyingPlayers()) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getTooManyPlayers()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getTooFastReconnect()));
|
||||
@@ -217,7 +243,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
|
||||
// We have to add this pipeline to monitor whenever the client disconnects
|
||||
// to remove them from the list of connected and queued players
|
||||
pipeline.addFirst(FALLBACK_HANDLER, new FallbackChannelHandler(loginRequest.getData()));
|
||||
pipeline.addFirst(FALLBACK_HANDLER, new FallbackChannelHandler(loginRequest.getData(), inetAddress));
|
||||
|
||||
// Queue the connection for further processing
|
||||
FALLBACK.getQueue().queue(inetAddress, () -> channel.eventLoop().execute(() -> {
|
||||
@@ -234,12 +260,9 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
pipeline.replace(
|
||||
TIMEOUT_HANDLER,
|
||||
TIMEOUT_HANDLER,
|
||||
new FallbackTimeoutHandler(Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
TimeUnit.MILLISECONDS)
|
||||
);
|
||||
final int readTimeout = Sonar.get().getConfig().getVerification().getReadTimeout();
|
||||
pipeline.replace(TIMEOUT_HANDLER, TIMEOUT_HANDLER,
|
||||
new FallbackTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
|
||||
|
||||
// Disconnect if the protocol version could not be resolved
|
||||
if (user.getProtocolVersion().isUnknown()) {
|
||||
@@ -298,10 +321,8 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
? FallbackPacketRegistry.CONFIG : FallbackPacketRegistry.GAME);
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
user.getPipeline().replace(
|
||||
PACKET_DECODER, FALLBACK_PACKET_DECODER, new FallbackPacketDecoder(user,
|
||||
new FallbackVerificationHandler(user, loginRequest.getData(), uuid)
|
||||
));
|
||||
user.getPipeline().replace(PACKET_DECODER, FALLBACK_PACKET_DECODER,
|
||||
new FallbackPacketDecoder(user, new FallbackVerificationHandler(user, loginRequest.getData(), uuid)));
|
||||
}));
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
@@ -309,7 +330,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
});
|
||||
}
|
||||
|
||||
private static final Map<Component, Kick> CACHED_KICK_PACKETS = new ConcurrentHashMap<>(16);
|
||||
private static final Map<Component, Kick> CACHED_KICK_PACKETS = new ConcurrentHashMap<>(10);
|
||||
|
||||
public static @NotNull Kick getKickPacket(final @NotNull Component component) {
|
||||
Kick cachedKickPacket = CACHED_KICK_PACKETS.get(component);
|
||||
|
||||
+3
-43
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -18,14 +18,11 @@
|
||||
package xyz.jonesdev.sonar.bungee.fallback;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
@@ -36,8 +33,8 @@ import java.util.Objects;
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
@SuppressWarnings("deprecation")
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void handle(final @NotNull LoginEvent event) {
|
||||
final InetAddress inetAddress = event.getConnection().getAddress().getAddress();
|
||||
|
||||
@@ -59,41 +56,4 @@ public final class FallbackListener implements Listener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@SuppressWarnings("deprecation")
|
||||
public void handle(final @NotNull PostLoginEvent event) {
|
||||
if (Sonar.get().getConfig().getLockdown().isEnabled()) {
|
||||
if (!event.getPlayer().hasPermission(Sonar.get().getConfig().getLockdown().getBypassPermission())) {
|
||||
final PendingConnection pendingConnection = event.getPlayer().getPendingConnection();
|
||||
// Try to close the channel with a custom serialized disconnect component
|
||||
if (pendingConnection instanceof FallbackInitialHandler) {
|
||||
final FallbackInitialHandler fallbackInitialHandler = (FallbackInitialHandler) pendingConnection;
|
||||
final Component component = Sonar.get().getConfig().getLockdown().getDisconnect();
|
||||
fallbackInitialHandler.closeWith(FallbackInitialHandler.getKickPacket(component));
|
||||
} else {
|
||||
// Fallback by disconnecting without a message
|
||||
pendingConnection.disconnect();
|
||||
Sonar.get().getLogger().warn("Fallback handler of {} is missing", event.getPlayer().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (Sonar.get().getConfig().getLockdown().isLogAttempts()) {
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getLockdown().getConsoleLog()
|
||||
.replace("%player%", event.getPlayer().getName())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getPendingConnection().getVersion())));
|
||||
}
|
||||
} else if (Sonar.get().getConfig().getLockdown().isNotifyAdmins()) {
|
||||
final Audience audience = Sonar.get().getVerboseHandler().getAudiences().get(event.getPlayer().getName());
|
||||
if (audience != null) {
|
||||
final String notification = Sonar.get().getConfig().getLockdown().getNotification();
|
||||
final Component deserialized = MiniMessage.miniMessage().deserialize(notification);
|
||||
audience.sendMessage(deserialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -23,7 +23,6 @@ import io.netty.util.ReferenceCountUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
@@ -34,9 +33,8 @@ import java.net.InetAddress;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackUserWrapper implements FallbackUser<ChannelWrapper, FallbackInitialHandler> {
|
||||
public final class FallbackUserWrapper implements FallbackUser<FallbackInitialHandler> {
|
||||
private final Fallback fallback;
|
||||
private final ChannelWrapper player;
|
||||
private final FallbackInitialHandler connection;
|
||||
private final Channel channel;
|
||||
private final ChannelPipeline pipeline;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -24,20 +24,21 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.InetAddress;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackChannelHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final @NotNull Fallback FALLBACK = Sonar.get().getFallback();
|
||||
private final @NotNull String username;
|
||||
private final @NotNull InetAddress inetAddress;
|
||||
|
||||
@Override
|
||||
public void channelUnregistered(final @NotNull ChannelHandlerContext ctx) {
|
||||
// Remove the username from the connected players
|
||||
FALLBACK.getConnected().remove(username);
|
||||
// Remove the IP address from the queue
|
||||
FALLBACK.getQueue().remove(((InetSocketAddress) ctx.channel().remoteAddress()).getAddress());
|
||||
|
||||
FALLBACK.getQueue().remove(inetAddress);
|
||||
// Make sure other handlers also know we've disconnected
|
||||
ctx.fireChannelUnregistered();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+160
-44
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -20,20 +20,25 @@ package xyz.jonesdev.sonar.common.fallback;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.Setter;
|
||||
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;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
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.ItemMapType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.map.MapInfoPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.map.PreparedMapInfo;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledged;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
import xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -44,22 +49,28 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackVerificationHandler implements FallbackPacketListener {
|
||||
private final @NotNull FallbackUser<?, ?> user;
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
// General
|
||||
private final SystemTimer login = new SystemTimer();
|
||||
private final @NotNull FallbackUser<?> user;
|
||||
private final String username;
|
||||
private final UUID playerUuid;
|
||||
private @NotNull State state = State.LOGIN_ACK; // 1.20.2
|
||||
|
||||
// Checks
|
||||
private short expectedTransactionId;
|
||||
private long expectedKeepAliveId;
|
||||
private int expectedTeleportId = -1;
|
||||
private int tick, totalReceivedPackets;
|
||||
private int ignoredMovementTicks;
|
||||
private int expectedKeepAliveId, expectedTeleportId = -1;
|
||||
private int tick, totalReceivedPackets, ignoredMovementTicks;
|
||||
private double posX, posY, posZ, lastY;
|
||||
private boolean resolvedClientBrand, resolvedClientSettings;
|
||||
@Setter
|
||||
private @NotNull State state = State.LOGIN_ACK;
|
||||
private boolean listenForMovements;
|
||||
|
||||
private final SystemTimer login = new SystemTimer();
|
||||
private static final Random RANDOM = new Random();
|
||||
// Map captcha
|
||||
private final SystemTimer keepAlive = new SystemTimer();
|
||||
private final SystemTimer actionBar = new SystemTimer();
|
||||
private @Nullable PreparedMapInfo captcha;
|
||||
private int captchaTriesLeft;
|
||||
|
||||
public enum State {
|
||||
// 1.20.2 configuration state
|
||||
@@ -70,11 +81,13 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
CLIENT_SETTINGS, PLUGIN_MESSAGE, TRANSACTION,
|
||||
// PLAY checks
|
||||
TELEPORT, POSITION,
|
||||
// Captcha
|
||||
MAP_CAPTCHA,
|
||||
// Done
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
public FallbackVerificationHandler(final @NotNull FallbackUser<?, ?> user,
|
||||
public FallbackVerificationHandler(final @NotNull FallbackUser<?> user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID playerUuid) {
|
||||
this.user = user;
|
||||
@@ -142,10 +155,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
final boolean v1_20_2 = user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0;
|
||||
if (!v1_20_2) {
|
||||
// Set the state to CLIENT_SETTINGS to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
// (1.20.2 needs the LOGIN_ACK state) and go on with
|
||||
// the flow of the verification.
|
||||
state = State.CLIENT_SETTINGS;
|
||||
}
|
||||
// Select the JoinGame packet for the respective protocol version
|
||||
// Send the JoinGame packet so we can continue the verification
|
||||
user.write(joinGame);
|
||||
if (v1_20_2) {
|
||||
// Perform the transaction check since the ClientSettings
|
||||
@@ -165,31 +179,47 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Teleport the player to the spawn position
|
||||
user.delayedWrite(new PositionLook(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
|
||||
0f, 0f, expectedTeleportId, false
|
||||
));
|
||||
0f, -90f, expectedTeleportId, false));
|
||||
// Make sure the player escapes the 1.18.2+ "Loading terrain" screen
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_18_2) >= 0) {
|
||||
user.delayedWrite(dynamicSpawnPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChunkData() {
|
||||
// Set the state to POSITION to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.POSITION;
|
||||
// 1.20.3 packets introduced game events
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_3) >= 0) {
|
||||
// Make sure the client knows that we're sending chunks next
|
||||
user.delayedWrite(START_WRITING_CHUNKS);
|
||||
}
|
||||
// Teleport player into the fake lobby by sending an empty chunk
|
||||
user.delayedWrite(EMPTY_CHUNK_DATA);
|
||||
// Send an UpdateSectionBlocks packet with a platform of blocks
|
||||
// to check if the player collides with the solid platform.
|
||||
user.delayedWrite(updateSectionBlocks);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
// Checking gravity is disabled, just finish verification
|
||||
if (!Sonar.get().getConfig().getVerification().getGravity().isEnabled()) {
|
||||
// Switch to captcha state if needed
|
||||
captchaOrFinish();
|
||||
} else {
|
||||
// Make sure the player knows we are checking them
|
||||
user.delayedWrite(youAreBeingChecked);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validateClientLocale(final @SuppressWarnings("unused") @NotNull FallbackUser<?, ?> user,
|
||||
private static boolean validateClientLocale(final @SuppressWarnings("unused") @NotNull FallbackUser<?> user,
|
||||
final String locale) {
|
||||
// Check the client locale by performing a simple regex check on it
|
||||
final Pattern pattern = Sonar.get().getConfig().getVerification().getValidLocaleRegex();
|
||||
return pattern.matcher(locale).matches(); // Disallow non-ascii characters (by default)
|
||||
}
|
||||
|
||||
private static boolean validateClientBrand(final @NotNull FallbackUser<?, ?> user, final ByteBuf content) {
|
||||
private static boolean validateClientBrand(final @NotNull FallbackUser<?> user, final ByteBuf content) {
|
||||
// We have to catch every DecoderException, so we can fail and punish
|
||||
// the player instead of only disconnecting them due to an exception.
|
||||
try {
|
||||
@@ -216,13 +246,60 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
// The player has already been verified, drop all other packets
|
||||
if (state == State.SUCCESS) return;
|
||||
// Check for timeout since the player could be sending packets but not important ones
|
||||
final long timeout = Sonar.get().getConfig().getVerification().getMaxPing();
|
||||
// We are expecting a captcha code in chat, drop all other packets
|
||||
if (state == State.MAP_CAPTCHA) {
|
||||
// Check if the player took too long to enter the captcha
|
||||
final int maxDuration = Sonar.get().getConfig().getVerification().getMap().getMaxDuration();
|
||||
checkFrame(!login.elapsed(maxDuration), "took too long to enter captcha");
|
||||
|
||||
// Handle incoming chat messages
|
||||
if (packet instanceof Chat) {
|
||||
final Chat chat = (Chat) packet;
|
||||
Objects.requireNonNull(captcha);
|
||||
if (!chat.getMessage().equals(captcha.getInfo().getAnswer())) {
|
||||
// Captcha is incorrect
|
||||
checkFrame(captchaTriesLeft-- > 0, "failed captcha too often");
|
||||
user.write(incorrectCaptcha);
|
||||
return;
|
||||
}
|
||||
// Captcha is correct
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Every second
|
||||
if (actionBar.elapsed(1000L)) {
|
||||
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 Chat(MiniMessage.miniMessage().deserialize(
|
||||
actionBarMessage.replace("%time-left%", timeLeft)), Chat.GAME_INFO_TYPE));
|
||||
// Make sure to reset the timer
|
||||
actionBar.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Every about 10 seconds
|
||||
if (keepAlive.elapsed(timeout)) {
|
||||
// Send the message again to remind the player
|
||||
user.delayedWrite(enterCodeMessage);
|
||||
// Send a KeepAlive packet to prevent timeout
|
||||
user.delayedWrite(CAPTCHA_KEEP_ALIVE);
|
||||
// Send both packets in one flush
|
||||
user.getChannel().flush();
|
||||
// Make sure to reset the timer
|
||||
keepAlive.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is not sending a ton of packets to the server
|
||||
final int maxPackets = Sonar.get().getConfig().getVerification().getMaxLoginPackets() + maxMovementTick;
|
||||
checkFrame(++totalReceivedPackets < maxPackets, "too many packets");
|
||||
|
||||
// Check for timeout since the player could be sending packets but not important ones
|
||||
final long timeout = Sonar.get().getConfig().getVerification().getMaxPing();
|
||||
// Check if the time limit has exceeded
|
||||
if (login.elapsed(timeout)) {
|
||||
user.getChannel().close();
|
||||
@@ -321,18 +398,6 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Check if the transaction ID is valid
|
||||
checkFrame(transaction.getId() == expectedTransactionId, "invalid transaction id");
|
||||
|
||||
// Checking gravity is disabled, just finish verification
|
||||
if (!Sonar.get().getConfig().getVerification().isCheckGravity()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: implement ChunkData packet for 1.20.2
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// First, send an Abilities packet to the client to make
|
||||
// sure the player falls even in spectator mode.
|
||||
// Then, teleport the player to the spawn position.
|
||||
@@ -422,7 +487,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// We have to account for this or the player will fail the verification.
|
||||
if (deltaY == 0) {
|
||||
// Check for too many ignored Y ticks
|
||||
final int maxIgnoredTicks = Sonar.get().getConfig().getVerification().getMaxIgnoredTicks();
|
||||
final int maxIgnoredTicks = Sonar.get().getConfig().getVerification().getGravity().getMaxIgnoredTicks();
|
||||
checkFrame(++ignoredMovementTicks < maxIgnoredTicks, "too many ignored ticks");
|
||||
return;
|
||||
}
|
||||
@@ -443,7 +508,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
|
||||
// The player is colliding to blocks, finish verification
|
||||
if (ground) {
|
||||
finish();
|
||||
captchaOrFinish();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -471,17 +536,46 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
tick++;
|
||||
}
|
||||
|
||||
private void assertState(final @NotNull State expectedState) {
|
||||
if (expectedState != state) {
|
||||
user.fail("expected " + expectedState + ", got " + state);
|
||||
private void captchaOrFinish() {
|
||||
if (user.getFallback().shouldDoMapCaptcha()) {
|
||||
if (MapInfoPreparer.getPreparedCAPTCHAs() == 0) {
|
||||
// This should not happen, but we have to return if there is no captcha prepared
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
|
||||
return;
|
||||
}
|
||||
// Set the state to MAP_CAPTCHA, so we don't handle any unnecessary packets
|
||||
state = State.MAP_CAPTCHA;
|
||||
if (!Sonar.get().getConfig().getVerification().getGravity().isEnabled()
|
||||
&& user.getProtocolVersion().compareTo(MINECRAFT_1_18_2) >= 0) {
|
||||
// Make sure the player escapes the 1.18.2+ "Loading terrain" screen
|
||||
user.delayedWrite(CAPTCHA_SPAWN_POSITION);
|
||||
}
|
||||
// Initialize the map captcha
|
||||
handleMapCaptcha();
|
||||
} else {
|
||||
// Finish the verification
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFrame(final boolean condition, final String message) {
|
||||
if (!condition) {
|
||||
user.fail(message);
|
||||
throw new CorruptedFrameException(message);
|
||||
}
|
||||
private void handleMapCaptcha() {
|
||||
// Reset max tries
|
||||
captchaTriesLeft = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
|
||||
|
||||
// Set slot to map
|
||||
user.delayedWrite(new SetSlot(0, 36, 1, 0,
|
||||
ItemMapType.FILLED_MAP.getId(user.getProtocolVersion()), SetSlot.MAP_NBT));
|
||||
// Send random captcha to the player
|
||||
captcha = MapInfoPreparer.getRandomCaptcha();
|
||||
Objects.requireNonNull(captcha).write(user);
|
||||
// Teleport the player to the position above the platform
|
||||
user.delayedWrite(CAPTCHA_POSITION);
|
||||
// Make sure the player cannot move
|
||||
user.delayedWrite(CAPTCHA_ABILITIES);
|
||||
// Make sure the player knows what to do
|
||||
user.delayedWrite(enterCodeMessage);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
@@ -504,4 +598,26 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", login.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails the verification if a certain state is unexpected.
|
||||
*
|
||||
* @param expectedState Expected state
|
||||
*/
|
||||
private void assertState(final @NotNull State expectedState) {
|
||||
checkFrame(state == expectedState, "expected " + expectedState + ", got " + state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a certain condition is met, fails the verification if not.
|
||||
*
|
||||
* @param condition Condition to fail if it's false
|
||||
* @param message Messages displayed in the stacktrace
|
||||
*/
|
||||
private void checkFrame(final boolean condition, final String message) {
|
||||
if (!condition) {
|
||||
user.fail(message);
|
||||
throw new CorruptedFrameException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -31,16 +31,15 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
|
||||
|
||||
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackUser<?> user;
|
||||
private final FallbackPacketListener listener;
|
||||
private @NotNull FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
public FallbackPacketDecoder(final @NotNull FallbackUser<?, ?> user,
|
||||
public FallbackPacketDecoder(final @NotNull FallbackUser<?> user,
|
||||
final @NotNull FallbackPacketListener listener) {
|
||||
this.user = user;
|
||||
this.registry = (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0 ? LOGIN : GAME)
|
||||
.getProtocolRegistry(SERVERBOUND, user.getProtocolVersion());
|
||||
this.listener = listener;
|
||||
updateRegistry(user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0 ? LOGIN : GAME);
|
||||
}
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry) {
|
||||
@@ -66,8 +65,7 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
final int packetId = readVarInt(byteBuf);
|
||||
final FallbackPacket packet = registry.createPacket(packetId);
|
||||
|
||||
// If the packet hasn't been found, skip it
|
||||
// TODO: Can we fail the verification afterwards?
|
||||
// If the packet isn't found, skip it
|
||||
if (packet == null) {
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
return;
|
||||
@@ -93,6 +91,9 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
|
||||
// Let our verification handler process the packet
|
||||
listener.handle(packet);
|
||||
|
||||
// Fire channel read to avoid timeout
|
||||
ctx.fireChannelRead(packet);
|
||||
} finally {
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+87
-35
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -23,7 +23,7 @@ import lombok.Data;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistrySync;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistryData;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledged;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccess;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
@@ -48,13 +48,13 @@ public enum FallbackPacketRegistry {
|
||||
CONFIG {
|
||||
{
|
||||
clientbound.register(Disconnect.class, Disconnect::new,
|
||||
map(0x01, MINECRAFT_1_20_2, false));
|
||||
map(0x01, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(FinishConfiguration.class, FinishConfiguration::new,
|
||||
map(0x02, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(RegistrySync.class, RegistrySync::new,
|
||||
map(0x05, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(RegistryData.class, RegistryData::new,
|
||||
map(0x05, MINECRAFT_1_20_2, true));
|
||||
|
||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x00, MINECRAFT_1_20_2, false));
|
||||
@@ -166,7 +166,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x40, MINECRAFT_1_19_1, true),
|
||||
map(0x3F, MINECRAFT_1_19_3, true),
|
||||
map(0x43, MINECRAFT_1_19_4, true),
|
||||
map(0x45, MINECRAFT_1_20_2, true));
|
||||
map(0x45, MINECRAFT_1_20_2, true),
|
||||
map(0x47, MINECRAFT_1_20_3, true));
|
||||
clientbound.register(Transaction.class, Transaction::new,
|
||||
map(0x32, MINECRAFT_1_7_2, true),
|
||||
map(0x11, MINECRAFT_1_9, true),
|
||||
@@ -180,19 +181,73 @@ public enum FallbackPacketRegistry {
|
||||
map(0x2E, MINECRAFT_1_19_3, true),
|
||||
map(0x32, MINECRAFT_1_19_4, true),
|
||||
map(0x33, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(DefaultSpawnPosition.class, DefaultSpawnPosition::new,
|
||||
map(0x05, MINECRAFT_1_7_2, true),
|
||||
map(0x43, MINECRAFT_1_9, true),
|
||||
map(0x45, MINECRAFT_1_12, true),
|
||||
map(0x46, MINECRAFT_1_12_1, true),
|
||||
map(0x49, MINECRAFT_1_13, true),
|
||||
map(0x4D, MINECRAFT_1_14, true),
|
||||
map(0x4E, MINECRAFT_1_15, true),
|
||||
map(0x42, MINECRAFT_1_16, true),
|
||||
map(0x4B, MINECRAFT_1_17, true),
|
||||
map(0x4A, MINECRAFT_1_19, true),
|
||||
map(0x4D, MINECRAFT_1_19_1, true),
|
||||
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));
|
||||
clientbound.register(MapData.class, MapData::new,
|
||||
map(0x34, MINECRAFT_1_7_2, true),
|
||||
map(0x24, MINECRAFT_1_9, true),
|
||||
map(0x26, MINECRAFT_1_13, true),
|
||||
map(0x27, MINECRAFT_1_15, true),
|
||||
map(0x26, MINECRAFT_1_16, true),
|
||||
map(0x25, MINECRAFT_1_16_2, true),
|
||||
map(0x27, MINECRAFT_1_17, true),
|
||||
map(0x24, MINECRAFT_1_19, true),
|
||||
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));
|
||||
clientbound.register(SetSlot.class, SetSlot::new,
|
||||
map(0x2F, MINECRAFT_1_7_2, true),
|
||||
map(0x16, MINECRAFT_1_9, true),
|
||||
map(0x17, MINECRAFT_1_13, true),
|
||||
map(0x16, MINECRAFT_1_14, true),
|
||||
map(0x17, MINECRAFT_1_15, true),
|
||||
map(0x16, MINECRAFT_1_16, true),
|
||||
map(0x15, MINECRAFT_1_16_2, true),
|
||||
map(0x16, MINECRAFT_1_17, true),
|
||||
map(0x13, MINECRAFT_1_19, true),
|
||||
map(0x12, MINECRAFT_1_19_3, true),
|
||||
map(0x14, MINECRAFT_1_19_4, true),
|
||||
map(0x15, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(Chat.class, Chat::new,
|
||||
map(0x02, MINECRAFT_1_7_2, true),
|
||||
map(0x0F, MINECRAFT_1_9, true),
|
||||
map(0x0E, MINECRAFT_1_13, true),
|
||||
map(0x0F, MINECRAFT_1_15, true),
|
||||
map(0x0E, MINECRAFT_1_16, true),
|
||||
map(0x0F, MINECRAFT_1_18_2, true),
|
||||
map(0x5F, MINECRAFT_1_19, true),
|
||||
map(0x62, MINECRAFT_1_19_1, true),
|
||||
map(0x60, MINECRAFT_1_19_3, true),
|
||||
map(0x64, MINECRAFT_1_19_4, true),
|
||||
map(0x67, MINECRAFT_1_20_2, true));
|
||||
map(0x02, MINECRAFT_1_7_2, false),
|
||||
map(0x0F, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_13, false),
|
||||
map(0x0F, MINECRAFT_1_15, false),
|
||||
map(0x0E, MINECRAFT_1_16, false),
|
||||
map(0x0F, MINECRAFT_1_17, false),
|
||||
map(0x5F, MINECRAFT_1_19, false),
|
||||
map(0x62, MINECRAFT_1_19_1, false),
|
||||
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));
|
||||
clientbound.register(GameEvent.class, GameEvent::new,
|
||||
map(0x20, MINECRAFT_1_20_3, true));
|
||||
|
||||
serverbound.register(Chat.class, Chat::new,
|
||||
map(0x01, MINECRAFT_1_7_2, false),
|
||||
map(0x02, MINECRAFT_1_9, false),
|
||||
map(0x03, MINECRAFT_1_12, false),
|
||||
map(0x02, MINECRAFT_1_12_1, false),
|
||||
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));
|
||||
serverbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false),
|
||||
map(0x0B, MINECRAFT_1_9, false),
|
||||
@@ -206,7 +261,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x12, MINECRAFT_1_19_1, false),
|
||||
map(0x11, MINECRAFT_1_19_3, false),
|
||||
map(0x12, MINECRAFT_1_19_4, false),
|
||||
map(0x14, MINECRAFT_1_20_2, false));
|
||||
map(0x14, MINECRAFT_1_20_2, false),
|
||||
map(0x15, MINECRAFT_1_20_3, false));
|
||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x15, MINECRAFT_1_7_2, false),
|
||||
map(0x04, MINECRAFT_1_9, false),
|
||||
@@ -244,7 +300,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x15, MINECRAFT_1_19_1, false),
|
||||
map(0x14, MINECRAFT_1_19_3, false),
|
||||
map(0x15, MINECRAFT_1_19_4, false),
|
||||
map(0x17, MINECRAFT_1_20_2, false));
|
||||
map(0x17, MINECRAFT_1_20_2, false),
|
||||
map(0x18, MINECRAFT_1_20_3, false));
|
||||
serverbound.register(TeleportConfirm.class, TeleportConfirm::new,
|
||||
map(0x00, MINECRAFT_1_9, false));
|
||||
serverbound.register(Position.class, Position::new,
|
||||
@@ -260,7 +317,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x14, MINECRAFT_1_19_1, false),
|
||||
map(0x13, MINECRAFT_1_19_3, false),
|
||||
map(0x14, MINECRAFT_1_19_4, false),
|
||||
map(0x16, MINECRAFT_1_20_2, false));
|
||||
map(0x16, MINECRAFT_1_20_2, false),
|
||||
map(0x17, MINECRAFT_1_20_3, false));
|
||||
serverbound.register(Player.class, Player::new,
|
||||
map(0x03, MINECRAFT_1_7_2, false),
|
||||
map(0x0F, MINECRAFT_1_9, false),
|
||||
@@ -274,7 +332,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x17, MINECRAFT_1_19_1, false),
|
||||
map(0x16, MINECRAFT_1_19_3, false),
|
||||
map(0x17, MINECRAFT_1_19_4, false),
|
||||
map(0x19, MINECRAFT_1_20_2, false));
|
||||
map(0x19, MINECRAFT_1_20_2, false),
|
||||
map(0x1A, MINECRAFT_1_20_3, false));
|
||||
serverbound.register(Transaction.class, Transaction::new,
|
||||
map(0x0F, MINECRAFT_1_7_2, false),
|
||||
map(0x05, MINECRAFT_1_9, false),
|
||||
@@ -287,7 +346,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x20, MINECRAFT_1_19_1, false),
|
||||
map(0x1F, MINECRAFT_1_19_3, false),
|
||||
map(0x20, MINECRAFT_1_19_4, false),
|
||||
map(0x23, MINECRAFT_1_20_2, false));
|
||||
map(0x23, MINECRAFT_1_20_2, false),
|
||||
map(0x24, MINECRAFT_1_20_3, false));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -308,14 +368,13 @@ public enum FallbackPacketRegistry {
|
||||
private final Map<ProtocolVersion, ProtocolRegistry> versions;
|
||||
|
||||
PacketRegistry() {
|
||||
Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class);
|
||||
for (ProtocolVersion version : ProtocolVersion.values()) {
|
||||
final Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class);
|
||||
for (final ProtocolVersion version : ProtocolVersion.values()) {
|
||||
if (!version.isLegacy() && !version.isUnknown()) {
|
||||
mutableVersions.put(version, new ProtocolRegistry(version));
|
||||
}
|
||||
}
|
||||
|
||||
versions = Collections.unmodifiableMap(mutableVersions);
|
||||
this.versions = Collections.unmodifiableMap(mutableVersions);
|
||||
}
|
||||
|
||||
ProtocolRegistry getProtocolRegistry(final ProtocolVersion version) {
|
||||
@@ -345,7 +404,7 @@ public enum FallbackPacketRegistry {
|
||||
break;
|
||||
}
|
||||
|
||||
final ProtocolRegistry registry = this.versions.get(protocol);
|
||||
final ProtocolRegistry registry = versions.get(protocol);
|
||||
if (registry == null) {
|
||||
throw new IllegalArgumentException("Unknown protocol version "
|
||||
+ current.protocolVersion);
|
||||
@@ -374,14 +433,7 @@ public enum FallbackPacketRegistry {
|
||||
private static ProtocolVersion getProtocolVersion(final @NotNull PacketMapping current,
|
||||
final @NotNull PacketMapping next,
|
||||
final @NotNull ProtocolVersion from) {
|
||||
final ProtocolVersion last = (ProtocolVersion) SUPPORTED_VERSIONS.toArray()[SUPPORTED_VERSIONS.size() - 1];
|
||||
final ProtocolVersion to = current == next ? last : next.protocolVersion;
|
||||
|
||||
if (from.compareTo(to) >= 0 && from != last) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Next mapping version (%s) should be lower then current (%s)", to, from));
|
||||
}
|
||||
return to;
|
||||
return current == next ? LATEST_VERSION : next.protocolVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -19,31 +19,41 @@ 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.MapInfoPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistrySync;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.Abilities;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.EmptyChunkData;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.JoinGame;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.UpdateSectionBlocks;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistryData;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
@UtilityClass
|
||||
public class FallbackPreparer {
|
||||
|
||||
// Abilities
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new Abilities((byte) 0, 0f, 0f);
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new Abilities(0x00, 0f, 0f);
|
||||
public final FallbackPacket CAPTCHA_ABILITIES = new Abilities(0x02, 0f, 0f);
|
||||
// Chunks
|
||||
public final FallbackPacket EMPTY_CHUNK_DATA = new EmptyChunkData(0, 0);
|
||||
// Finish Configuration
|
||||
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfiguration();
|
||||
// Synchronize Registry
|
||||
public final FallbackPacket REGISTRY_SYNC = new RegistrySync();
|
||||
public final FallbackPacket REGISTRY_SYNC = new RegistryData();
|
||||
// Keep Alive
|
||||
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlive(0L);
|
||||
// Game Event (1.20.3+)
|
||||
public final FallbackPacket START_WRITING_CHUNKS = new GameEvent(13, 0);
|
||||
// Chat
|
||||
public FallbackPacket enterCodeMessage;
|
||||
public FallbackPacket youAreBeingChecked;
|
||||
public FallbackPacket incorrectCaptcha;
|
||||
// JoinGame
|
||||
public FallbackPacket joinGame;
|
||||
// Update Section Blocks
|
||||
public FallbackPacket updateSectionBlocks;
|
||||
// Default Spawn Position
|
||||
public FallbackPacket dynamicSpawnPosition;
|
||||
|
||||
// Collisions
|
||||
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (protocol maximum)
|
||||
@@ -51,6 +61,13 @@ public class FallbackPreparer {
|
||||
public final int SPAWN_Z_POSITION = 16 / 2; // middle of the chunk
|
||||
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is the maximum Y position allowed
|
||||
|
||||
// Captcha position
|
||||
public final FallbackPacket CAPTCHA_POSITION = new PositionLook(
|
||||
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION, 0f, 90f, 0, false);
|
||||
public final FallbackPacket CAPTCHA_SPAWN_POSITION = new DefaultSpawnPosition(
|
||||
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION);
|
||||
|
||||
// Blocks
|
||||
private final ChangedBlock[] CHANGED_BLOCKS = new ChangedBlock[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
|
||||
|
||||
public int maxMovementTick, dynamicSpawnYPosition;
|
||||
@@ -58,8 +75,8 @@ public class FallbackPreparer {
|
||||
public double maxFallDistance;
|
||||
|
||||
public void prepare() {
|
||||
joinGame = new JoinGame(5,
|
||||
Sonar.get().getConfig().getVerification().getGamemode().getId(),
|
||||
joinGame = new JoinGame(0,
|
||||
Sonar.get().getConfig().getVerification().getGravity().getGamemode().getId(),
|
||||
0,
|
||||
false,
|
||||
0,
|
||||
@@ -70,7 +87,7 @@ public class FallbackPreparer {
|
||||
"minecraft:overworld");
|
||||
|
||||
maxFallDistance = 0;
|
||||
maxMovementTick = Sonar.get().getConfig().getVerification().getMaxMovementTicks();
|
||||
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
|
||||
preparedCachedYMotions = new double[maxMovementTick + 8];
|
||||
|
||||
for (int i = 0; i < preparedCachedYMotions.length; i++) {
|
||||
@@ -81,6 +98,7 @@ 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 DefaultSpawnPosition(SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION);
|
||||
|
||||
// Prepare collision platform positions
|
||||
int index = 0;
|
||||
@@ -90,13 +108,25 @@ public class FallbackPreparer {
|
||||
x + (BLOCKS_PER_ROW / 2),
|
||||
DEFAULT_Y_COLLIDE_POSITION,
|
||||
z + (BLOCKS_PER_ROW / 2),
|
||||
0, 0
|
||||
);
|
||||
0, 0);
|
||||
CHANGED_BLOCKS[index++] = new ChangedBlock(position, BlockType.BARRIER);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare UpdateSectionBlocks packet
|
||||
updateSectionBlocks = new UpdateSectionBlocks(0, 0, CHANGED_BLOCKS);
|
||||
|
||||
// "You are being checked" message
|
||||
if (Sonar.get().getConfig().getVerification().getGravity().isEnabled()) {
|
||||
youAreBeingChecked = new Chat(Sonar.get().getConfig().getVerification().getGravity().getYouAreBeingChecked());
|
||||
}
|
||||
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER) {
|
||||
enterCodeMessage = new Chat(Sonar.get().getConfig().getVerification().getMap().getEnterCode());
|
||||
incorrectCaptcha = new Chat(Sonar.get().getConfig().getVerification().getMap().getFailedCaptcha());
|
||||
|
||||
// Precompute captcha answers
|
||||
MapInfoPreparer.prepare();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+7
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -28,7 +28,10 @@ import java.util.function.Function;
|
||||
public enum BlockType {
|
||||
STONE(protocolVersion -> 1),
|
||||
BARRIER(protocolVersion -> {
|
||||
// Link: https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/1.20/blocks.json
|
||||
// Useful resources:
|
||||
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
|
||||
// - https://pokechu22.github.io/Burger/1.20.4.html
|
||||
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
|
||||
switch (protocolVersion) {
|
||||
case MINECRAFT_1_7_2:
|
||||
case MINECRAFT_1_7_6:
|
||||
@@ -73,7 +76,8 @@ public enum BlockType {
|
||||
case MINECRAFT_1_20:
|
||||
return 10225;
|
||||
case MINECRAFT_1_20_2:
|
||||
return 10365;
|
||||
case MINECRAFT_1_20_3:
|
||||
return 10366;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+7
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -24,18 +24,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
public final class ChangedBlock {
|
||||
private final @NotNull BlockPosition position;
|
||||
private final BlockType type;
|
||||
private final int legacyChunkPosCrammed;
|
||||
private final int modernChunkPosCrammed;
|
||||
private final int legacyBlockState, blockState;
|
||||
|
||||
public ChangedBlock(final @NotNull BlockPosition position, final BlockType type) {
|
||||
this.position = position;
|
||||
this.type = type;
|
||||
this.legacyChunkPosCrammed = position.getX()
|
||||
- (position.getChunkX() << 4) << 12 | position.getZ()
|
||||
- (position.getChunkZ() << 4) << 8 | position.getY();
|
||||
this.modernChunkPosCrammed = position.getX()
|
||||
- (position.getChunkX() << 4) << 8 | position.getZ()
|
||||
- (position.getChunkZ() << 4) << 4 | position.getY()
|
||||
- ((position.getY() >> 4) << 4);
|
||||
final int x = position.getX() - (position.getChunkX() << 4);
|
||||
final int y = position.getY();
|
||||
final int z = position.getZ() - (position.getChunkZ() << 4);
|
||||
this.legacyBlockState = x << 12 | z << 8 | y;
|
||||
this.blockState = x << 8 | z << 4 | y - ((y >> 4) << 4);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RequiredArgsConstructor
|
||||
public enum ItemMapType {
|
||||
FILLED_MAP(protocolVersion -> {
|
||||
// Useful resources:
|
||||
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
|
||||
// - https://pokechu22.github.io/Burger/1.20.4.html
|
||||
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
|
||||
switch (protocolVersion) {
|
||||
default:
|
||||
// 1.7-1.12.2
|
||||
return 358;
|
||||
case MINECRAFT_1_13:
|
||||
case MINECRAFT_1_13_1:
|
||||
return 608;
|
||||
case MINECRAFT_1_13_2:
|
||||
return 613;
|
||||
case MINECRAFT_1_14:
|
||||
case MINECRAFT_1_14_1:
|
||||
case MINECRAFT_1_14_2:
|
||||
case MINECRAFT_1_14_3:
|
||||
case MINECRAFT_1_14_4:
|
||||
case MINECRAFT_1_15:
|
||||
case MINECRAFT_1_15_1:
|
||||
case MINECRAFT_1_15_2:
|
||||
return 671;
|
||||
case MINECRAFT_1_16:
|
||||
case MINECRAFT_1_16_1:
|
||||
case MINECRAFT_1_16_2:
|
||||
case MINECRAFT_1_16_3:
|
||||
case MINECRAFT_1_16_4:
|
||||
return 733;
|
||||
case MINECRAFT_1_17:
|
||||
case MINECRAFT_1_17_1:
|
||||
case MINECRAFT_1_18:
|
||||
case MINECRAFT_1_18_2:
|
||||
return 847;
|
||||
case MINECRAFT_1_19:
|
||||
case MINECRAFT_1_19_1:
|
||||
return 886;
|
||||
case MINECRAFT_1_19_3:
|
||||
return 914;
|
||||
case MINECRAFT_1_19_4:
|
||||
return 937;
|
||||
case MINECRAFT_1_20:
|
||||
case MINECRAFT_1_20_2:
|
||||
return 941;
|
||||
case MINECRAFT_1_20_3:
|
||||
return 979;
|
||||
}
|
||||
});
|
||||
|
||||
private final Function<ProtocolVersion, Integer> function;
|
||||
|
||||
public int getId(final @NotNull ProtocolVersion protocolVersion) {
|
||||
return function.apply(protocolVersion);
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -15,12 +15,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.bungee.fallback;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.map;
|
||||
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
final class ConditionFailedException extends DecoderException {
|
||||
public ConditionFailedException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class MapInfo {
|
||||
private final String answer;
|
||||
private final int columns, rows;
|
||||
private final int x, y;
|
||||
private final byte[] buffer;
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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 lombok.Getter;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static java.awt.Font.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.map.PreparedMapInfo.DIMENSIONS;
|
||||
|
||||
@UtilityClass
|
||||
public class MapInfoPreparer {
|
||||
private final Random RANDOM = new Random();
|
||||
|
||||
private final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final int[] FONT_STYLES = new int[]{
|
||||
PLAIN,
|
||||
BOLD,
|
||||
ITALIC,
|
||||
ITALIC | BOLD
|
||||
};
|
||||
|
||||
private final int[][] COLOR_PALETTE = new int[][]{
|
||||
new int[]{ // Blue
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51
|
||||
},
|
||||
new int[]{ // Orange
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
63,
|
||||
},
|
||||
new int[]{ // Green
|
||||
4,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
},
|
||||
new int[]{ // Black
|
||||
27,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
},
|
||||
new int[]{ // Red
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19
|
||||
}
|
||||
};
|
||||
|
||||
private PreparedMapInfo[] cached;
|
||||
@Getter
|
||||
private int preparedCAPTCHAs;
|
||||
private boolean currentlyPreparing;
|
||||
|
||||
public PreparedMapInfo getRandomCaptcha() {
|
||||
return cached[RANDOM.nextInt(preparedCAPTCHAs)];
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
final SystemTimer timer = new SystemTimer();
|
||||
Sonar.get().getLogger().info("Precomputing map captcha answers...");
|
||||
|
||||
// Make sure we're not running into concurrency issues
|
||||
if (currentlyPreparing) {
|
||||
Sonar.get().getLogger().warn("Did not precompute map captcha answers as another task is running.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset all statistics
|
||||
preparedCAPTCHAs = 0;
|
||||
currentlyPreparing = true;
|
||||
final SonarConfiguration.Verification.Map config = Sonar.get().getConfig().getVerification().getMap();
|
||||
cached = null;
|
||||
cached = new PreparedMapInfo[config.getPrecomputeAmount()];
|
||||
|
||||
// Prepare fonts from config
|
||||
final String dictionary = config.getDictionary();
|
||||
final List<String> fonts = config.getFonts();
|
||||
if (fonts.isEmpty()) {
|
||||
Sonar.get().getLogger().warn("No fonts found, using fallback font...");
|
||||
fonts.add(DIALOG);
|
||||
}
|
||||
final String[] fontTypes = fonts.toArray(new String[0]);
|
||||
|
||||
// Prepare distortions values from config
|
||||
final double distortionsFactorX = config.getDistortionsFactorX();
|
||||
final double distortionsFactorY = config.getDistortionsFactorY();
|
||||
final double halfDistortionsFactorX = distortionsFactorX / 2D;
|
||||
final double halfDistortionsFactorY = distortionsFactorY / 2D;
|
||||
|
||||
for (int _i = 0; _i < cached.length; _i++) {
|
||||
final int currentIndex = _i;
|
||||
final byte[] buffer = new byte[PreparedMapInfo.SCALE];
|
||||
PREPARATION_SERVICE.execute(() -> {
|
||||
// Create image
|
||||
final BufferedImage image = new BufferedImage(DIMENSIONS, DIMENSIONS, BufferedImage.TYPE_3BYTE_BGR);
|
||||
final Graphics2D graphics = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
// Create random font
|
||||
final String fontType = fontTypes[RANDOM.nextInt(fontTypes.length)];
|
||||
final int fontStyle = FONT_STYLES[RANDOM.nextInt(FONT_STYLES.length)];
|
||||
final int fontSize = 27
|
||||
+ (config.isRandomizeFontSize()
|
||||
? RANDOM.nextInt(6) - 2 : 5);
|
||||
@SuppressWarnings("all") final Font answerFont = new Font(fontType, fontStyle, fontSize);
|
||||
graphics.setFont(answerFont);
|
||||
|
||||
// Build answer to the captcha
|
||||
final StringBuilder answerBuilder = new StringBuilder();
|
||||
for (int _j = 0; _j < 5; _j++) {
|
||||
answerBuilder.append(dictionary.charAt(RANDOM.nextInt(dictionary.length())));
|
||||
}
|
||||
final String answer = answerBuilder.toString();
|
||||
|
||||
// Calculate text position
|
||||
final int stringWidth = graphics.getFontMetrics().stringWidth(answer);
|
||||
final int halfWidth = image.getWidth() / 2;
|
||||
final int halfHeight = image.getHeight() / 2;
|
||||
final int spacing = 5;
|
||||
double _x = halfWidth - stringWidth / 2f - spacing;
|
||||
double _y = halfHeight + fontSize / 3f;
|
||||
|
||||
// Draw each character one by one
|
||||
final FontRenderContext fontRenderContext = graphics.getFontRenderContext();
|
||||
for (final char c : answer.toCharArray()) {
|
||||
// Randomize x and y
|
||||
if (config.isRandomizePositions()) {
|
||||
_y += RANDOM.nextInt(8) - 4;
|
||||
}
|
||||
|
||||
// Apply random distortion
|
||||
final double distortionFactorX = RANDOM.nextDouble() * distortionsFactorX - halfDistortionsFactorX;
|
||||
final double distortionFactorY = RANDOM.nextDouble() * distortionsFactorY - halfDistortionsFactorY;
|
||||
|
||||
// Create a GlyphVector for the character
|
||||
final String character = String.valueOf(c);
|
||||
final GlyphVector glyphVector = graphics.getFont().createGlyphVector(fontRenderContext, character);
|
||||
|
||||
// Apply the distortion to the GlyphVector using AffineTransform
|
||||
final AffineTransform transform = AffineTransform.getTranslateInstance(_x, _y);
|
||||
transform.shear(distortionFactorX, distortionFactorY);
|
||||
final Shape distortedCharacter = glyphVector.getOutline();
|
||||
final Shape distorted = transform.createTransformedShape(distortedCharacter);
|
||||
|
||||
// Draw the distorted character
|
||||
graphics.fill(distorted);
|
||||
|
||||
// Update x by width
|
||||
_x += glyphVector.getVisualBounds().getWidth() + spacing;
|
||||
}
|
||||
|
||||
// TODO: Color converter
|
||||
graphics.setColor(Color.RED);
|
||||
|
||||
// Draw random lines
|
||||
for (int i = 0; i < config.getRandomLinesAmount(); i++) {
|
||||
final int startX = RANDOM.nextInt(halfWidth);
|
||||
final int startY = RANDOM.nextInt(halfHeight);
|
||||
final int endX = halfWidth + RANDOM.nextInt(halfWidth);
|
||||
final int endY = halfHeight + RANDOM.nextInt(halfHeight);
|
||||
|
||||
graphics.drawLine(startX, startY, endX, endY);
|
||||
}
|
||||
|
||||
// Draw random ovals
|
||||
for (int i = 0; i < config.getRandomOvalsAmount(); i++) {
|
||||
final int startX = RANDOM.nextInt(halfWidth);
|
||||
final int startY = RANDOM.nextInt(halfHeight);
|
||||
final int endX = halfWidth + RANDOM.nextInt(halfWidth);
|
||||
final int endY = halfHeight + RANDOM.nextInt(halfHeight);
|
||||
|
||||
graphics.drawOval(startX, startY, endX, endY);
|
||||
}
|
||||
|
||||
// Select random color palette
|
||||
final int[] colorPalette = COLOR_PALETTE[currentIndex % COLOR_PALETTE.length];
|
||||
// Select the next color palette for geometry
|
||||
final int[] nextColorPalette = COLOR_PALETTE[(currentIndex + 1) % COLOR_PALETTE.length];
|
||||
// Clear background
|
||||
Arrays.fill(buffer, (byte) 57);
|
||||
// Color every pixel individually
|
||||
final int spacingY = halfHeight - fontSize;
|
||||
for (int x = spacing; x < image.getWidth() - spacing; x++) {
|
||||
for (int y = spacingY; y < image.getHeight() - spacingY; y++) {
|
||||
final int pixel = image.getRGB(x, y);
|
||||
final int index = y * image.getWidth() + x;
|
||||
if (pixel == -16777216 && RANDOM.nextInt(100) < 97) continue;
|
||||
// Set color of pixel to random color from the palette
|
||||
final byte color = (byte) (pixel == -16777216 ? RANDOM.nextInt(100) < 75 ? 14 :
|
||||
RANDOM.nextInt(Byte.MAX_VALUE)
|
||||
: pixel != -65536 ? colorPalette[RANDOM.nextInt(colorPalette.length)]
|
||||
: nextColorPalette[RANDOM.nextInt(nextColorPalette.length)]);
|
||||
// Write pixel color to buffer
|
||||
buffer[index] = color;
|
||||
}
|
||||
}
|
||||
// Cache buffer to map
|
||||
cached[currentIndex] = new PreparedMapInfo(answer, image.getWidth(), image.getHeight(), buffer);
|
||||
} finally {
|
||||
// Dispose graphics
|
||||
graphics.dispose();
|
||||
// Finished?
|
||||
if (currentIndex == cached.length - 1) {
|
||||
Sonar.get().getLogger().info("Successfully precomputed map captcha answers in {}s!", timer);
|
||||
currentlyPreparing = false;
|
||||
}
|
||||
preparedCAPTCHAs++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.MapData;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
|
||||
|
||||
@Getter
|
||||
public final class PreparedMapInfo {
|
||||
public static final int DIMENSIONS = (int) Math.pow(2, 7);
|
||||
public static final int SCALE = DIMENSIONS * DIMENSIONS;
|
||||
|
||||
private final MapInfo info;
|
||||
private final MapData[] legacy;
|
||||
private final MapData modern;
|
||||
|
||||
public PreparedMapInfo(final String answer,
|
||||
final int columns, final int rows,
|
||||
final byte @NotNull [] buffer) {
|
||||
this.info = new MapInfo(answer, columns, rows, 0, 0, buffer);
|
||||
|
||||
// Prepare 1.7 map data using a grid
|
||||
final byte[][] grid = new byte[DIMENSIONS][DIMENSIONS];
|
||||
for (int i = 0; i < buffer.length; i++) {
|
||||
final byte buf = buffer[i];
|
||||
grid[i & Byte.MAX_VALUE][i >> 7] = buf;
|
||||
}
|
||||
this.legacy = new MapData[grid.length];
|
||||
for (int i = 0; i < grid.length; i++) {
|
||||
this.legacy[i] = new MapData(new MapInfo(answer, DIMENSIONS, DIMENSIONS, i, 0, grid[i]));
|
||||
}
|
||||
|
||||
// Prepare 1.8+ map data
|
||||
this.modern = new MapData(info);
|
||||
}
|
||||
|
||||
public void write(final @NotNull FallbackUser<?> user) {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
for (final MapData data : legacy) {
|
||||
user.delayedWrite(data);
|
||||
}
|
||||
} else {
|
||||
user.delayedWrite(modern);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+5
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -18,33 +18,26 @@
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.config;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
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.dimension.DimensionRegistry;
|
||||
|
||||
import java.io.IOException;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public final class RegistrySync implements FallbackPacket {
|
||||
public final class RegistryData implements FallbackPacket {
|
||||
|
||||
@Override
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
try (final ByteBufOutputStream stream = new ByteBufOutputStream(byteBuf)) {
|
||||
stream.writeByte(10); // CompoundTag ID
|
||||
BinaryTagTypes.COMPOUND.write(DimensionRegistry.CODEC_1_20, stream);
|
||||
} catch (IOException exception) {
|
||||
throw new EncoderException("Cannot write NBT CompoundTag");
|
||||
}
|
||||
writeNamelessCompoundTag(byteBuf, DimensionRegistry.CODEC_1_20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -31,7 +31,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Abilities implements FallbackPacket {
|
||||
private byte encodedFlags;
|
||||
private int encodedFlags;
|
||||
private float flySpeed, walkSpeed;
|
||||
|
||||
@Override
|
||||
|
||||
+115
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -17,48 +17,88 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeUUID;
|
||||
import static xyz.jonesdev.sonar.common.utility.component.ComponentSerializer.serialize;
|
||||
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;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class Chat implements FallbackPacket {
|
||||
private static final UUID PLACEHOLDER_UUID = new UUID(0L, 0L);
|
||||
private static final int DIV_FLOOR = -Math.floorDiv(-20, 8);
|
||||
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
public static final byte SYSTEM_TYPE = (byte) 1;
|
||||
public static final byte GAME_INFO_TYPE = (byte) 2;
|
||||
|
||||
private Component component;
|
||||
private byte position;
|
||||
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;
|
||||
|
||||
// 1.20.3
|
||||
private BinaryTag nbtMessage;
|
||||
|
||||
// Clientbound LegacyChat
|
||||
public Chat(final Component component) {
|
||||
this(component, SYSTEM_TYPE);
|
||||
}
|
||||
|
||||
// Clientbound LegacyChat
|
||||
public Chat(final Component component, final byte type) {
|
||||
this(component, JSONComponentSerializer.json().serialize(component), type);
|
||||
}
|
||||
|
||||
public Chat(final Component component, final String message, final byte type) {
|
||||
this(component, message, type,
|
||||
false, false, null, null,
|
||||
0L, false, null, serialize(new JsonParser().parse(message)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
final String serialized = JSONComponentSerializer.json().serialize(component);
|
||||
writeString(byteBuf, serialized);
|
||||
|
||||
// Message type
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
byteBuf.writeBoolean(position == 2);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
writeVarInt(byteBuf, position);
|
||||
// Serialized message
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20_3) >= 0) {
|
||||
writeNamelessCompoundTag(byteBuf, nbtMessage);
|
||||
} else {
|
||||
byteBuf.writeByte(this.position);
|
||||
writeString(byteBuf, message);
|
||||
}
|
||||
|
||||
// UUID
|
||||
// Type
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
byteBuf.writeBoolean(type == GAME_INFO_TYPE);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
writeVarInt(byteBuf, type);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
byteBuf.writeByte(type);
|
||||
}
|
||||
|
||||
// Sender
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19) < 0) {
|
||||
writeUUID(byteBuf, PLACEHOLDER_UUID);
|
||||
@@ -66,7 +106,62 @@ public final class Chat implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
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();
|
||||
final long saltLong = byteBuf.readLong();
|
||||
final byte[] signatureBytes = readByteArray(byteBuf);
|
||||
|
||||
if (saltLong != 0L && signatureBytes.length > 0) {
|
||||
signature = signatureBytes;
|
||||
expiry = Instant.ofEpochMilli(expiresAt);
|
||||
} else if ((protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0
|
||||
|| saltLong == 0L) && signatureBytes.length == 0) {
|
||||
unsigned = true;
|
||||
} else {
|
||||
throw new CorruptedFrameException("Invalid signature");
|
||||
}
|
||||
|
||||
signedPreview = byteBuf.readBoolean();
|
||||
if (signedPreview && unsigned) {
|
||||
throw new CorruptedFrameException("Signature missing");
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||
final int size = readVarInt(byteBuf);
|
||||
if (size < 0 || size > 5) {
|
||||
throw new CorruptedFrameException("Invalid previous messages");
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
readUUID(byteBuf);
|
||||
readByteArray(byteBuf);
|
||||
}
|
||||
|
||||
if (byteBuf.readBoolean()) {
|
||||
readUUID(byteBuf);
|
||||
readByteArray(byteBuf);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timestamp = Instant.ofEpochMilli(byteBuf.readLong());
|
||||
salt = byteBuf.readLong();
|
||||
signed = byteBuf.readBoolean();
|
||||
if (signed) {
|
||||
byte[] sign = new byte[256];
|
||||
byteBuf.readBytes(sign);
|
||||
signature = sign;
|
||||
} else {
|
||||
signature = new byte[0];
|
||||
}
|
||||
|
||||
readVarInt(byteBuf);
|
||||
byte[] bytes = new byte[DIV_FLOOR];
|
||||
byteBuf.readBytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_14;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_17;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class DefaultSpawnPosition implements FallbackPacket {
|
||||
private int x, y, z;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeInt(x);
|
||||
byteBuf.writeInt(y);
|
||||
byteBuf.writeInt(z);
|
||||
} else {
|
||||
final long encoded = protocolVersion.compareTo(MINECRAFT_1_14) < 0
|
||||
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
|
||||
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
|
||||
|
||||
byteBuf.writeLong(encoded);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
|
||||
byteBuf.writeFloat(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+15
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -29,8 +31,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.utility.component.ComponentSerializer.serialize;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
|
||||
|
||||
@Getter
|
||||
@@ -39,10 +41,15 @@ import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeStrin
|
||||
@AllArgsConstructor
|
||||
public final class Disconnect implements FallbackPacket {
|
||||
private @Nullable String reason;
|
||||
private BinaryTag binaryTag;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
writeString(byteBuf, Objects.requireNonNull(reason));
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) {
|
||||
writeNamelessCompoundTag(byteBuf, binaryTag);
|
||||
} else {
|
||||
writeString(byteBuf, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,6 +58,8 @@ public final class Disconnect implements FallbackPacket {
|
||||
}
|
||||
|
||||
public static @NotNull Disconnect create(final Component component) {
|
||||
return new Disconnect(JSONComponentSerializer.json().serialize(component));
|
||||
final String serialized = JSONComponentSerializer.json().serialize(component);
|
||||
final BinaryTag binaryTag = serialize(new JsonParser().parse(serialized));
|
||||
return new Disconnect(serialized, binaryTag);
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Sonar Contributors
|
||||
* 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
|
||||
@@ -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.writeArray;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeCompoundTag;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
@Getter
|
||||
@@ -92,7 +91,11 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
|
||||
writeCompoundTag(byteBuf, protocolVersion.compareTo(MINECRAFT_1_18) < 0 ? LEGACY_TAG : MODERN_TAG);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
|
||||
writeNamelessCompoundTag(byteBuf, MODERN_TAG);
|
||||
} else {
|
||||
writeCompoundTag(byteBuf, protocolVersion.compareTo(MINECRAFT_1_18) < 0 ? LEGACY_TAG : MODERN_TAG);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0 && protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
|
||||
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user