mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 09:20:15 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0137fdf7e9 | ||
|
|
df14e42a0b | ||
|
|
c30dd0225b | ||
|
|
626fa422bc | ||
|
|
f08d9239fe | ||
|
|
1edd71df04 | ||
|
|
aff3bd4a64 | ||
|
|
ce1d623d60 | ||
|
|
77716c0fcc | ||
|
|
c34a3d7dc2 | ||
|
|
4fdb7cc70c | ||
|
|
7ddca3e152 | ||
|
|
e01a226475 | ||
|
|
21d983362e | ||
|
|
ab5203f4c5 | ||
|
|
03c209829c | ||
|
|
af57d6de42 | ||
|
|
d1f8ab4f10 |
No files matched your search
+2
-1
@@ -1,3 +1,4 @@
|
||||
org.gradle.daemon=false
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.37
|
||||
version=2.1.0
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
netty = "4.1.111.Final"
|
||||
netty = "4.1.112.Final"
|
||||
adventure = "4.17.0"
|
||||
adventure-platform = "4.3.3"
|
||||
adventure-platform = "4.3.4"
|
||||
bstats = "3.0.2"
|
||||
libby = "2.0.0-SNAPSHOT"
|
||||
|
||||
@@ -16,8 +16,6 @@ adventure-serializer = { module = "net.kyori:adventure-text-serializer-gson", ve
|
||||
adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" }
|
||||
adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" }
|
||||
adventure-platform-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform" }
|
||||
adventure-platform-facet = { module = "net.kyori:adventure-platform-facet", version.ref = "adventure-platform" }
|
||||
adventure-platform-api = { module = "net.kyori:adventure-platform-api", version.ref = "adventure-platform" }
|
||||
libby-core = { module = "com.alessiodp.libby:libby-core", version.ref = "libby" }
|
||||
libby-bungee = { module = "com.alessiodp.libby:libby-bungee", version.ref = "libby" }
|
||||
libby-bukkit = { module = "com.alessiodp.libby:libby-bukkit", version.ref = "libby" }
|
||||
@@ -26,10 +24,12 @@ bstats-bungee = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats
|
||||
bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" }
|
||||
bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" }
|
||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
|
||||
annotations = "org.jetbrains:annotations:24.0.1"
|
||||
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
|
||||
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
|
||||
lombok = "org.projectlombok:lombok:1.18.34"
|
||||
capja = "xyz.jonesdev:capja:1.0.3"
|
||||
bungeecord = "net.md_5:bungeecord-proxy:master-SNAPSHOT"
|
||||
velocity = "com.velocitypowered:velocity-proxy:3.3.0-SNAPSHOT"
|
||||
# We have to use 1.8 for backwards compatibility
|
||||
# We have to use 1.8.8 for backwards compatibility
|
||||
spigot = "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
Vendored
+2
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
||||
@@ -3,11 +3,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4") {
|
||||
compileOnly(rootProject.libs.simpleyaml) {
|
||||
exclude(group = "org.yaml")
|
||||
}
|
||||
|
||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
||||
compileOnly(rootProject.libs.annotations)
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
@@ -46,6 +46,9 @@ public interface Sonar {
|
||||
@ApiStatus.Internal
|
||||
@Nullable Audience audience(final @Nullable UUID uniqueId);
|
||||
|
||||
@ApiStatus.Internal
|
||||
@NotNull Audience sender(final @NotNull Object object);
|
||||
|
||||
/**
|
||||
* @return The platform the plugin is being run on
|
||||
*/
|
||||
|
||||
@@ -17,19 +17,30 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api;
|
||||
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum SonarPlatform {
|
||||
BUKKIT("Bukkit", 19110),
|
||||
BUNGEE("BungeeCord", 19109),
|
||||
VELOCITY("Velocity", 19107);
|
||||
BUKKIT("Bukkit", 19110,
|
||||
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder",
|
||||
pipeline -> "packet_handler"),
|
||||
BUNGEE("BungeeCord", 19109,
|
||||
pipeline -> "packet-encoder",
|
||||
pipeline -> "inbound-boss"),
|
||||
VELOCITY("Velocity", 19107,
|
||||
pipeline -> "minecraft-encoder",
|
||||
pipeline -> "handler");
|
||||
|
||||
private final String displayName;
|
||||
/**
|
||||
* bStats service ID for the respective Sonar platform
|
||||
*/
|
||||
private final int metricsId;
|
||||
private final Function<ChannelPipeline, String> encoderFunction;
|
||||
private final Function<ChannelPipeline, String> handlerFunction;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Getter
|
||||
enum Language {
|
||||
public enum Language {
|
||||
SYSTEM("system"),
|
||||
AB("Abkhazian"),
|
||||
AA("Afar"),
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.config;
|
||||
|
||||
import com.alessiodp.libby.Library;
|
||||
import com.j256.ormlite.db.DatabaseType;
|
||||
import lombok.*;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@@ -48,6 +49,8 @@ public final class SonarConfiguration {
|
||||
private final SimpleYamlConfig generalConfig, messagesConfig, webhookConfig;
|
||||
@Getter
|
||||
private final File languageFile, pluginFolder;
|
||||
@Getter
|
||||
private Language language;
|
||||
|
||||
static final LoggerWrapper LOGGER = new LoggerWrapper() {
|
||||
|
||||
@@ -121,27 +124,27 @@ public final class SonarConfiguration {
|
||||
}
|
||||
|
||||
// Generate the language file and check what it's set to
|
||||
Language preferredLanguage = getPreferredLanguage();
|
||||
if (preferredLanguage == Language.SYSTEM) {
|
||||
language = getPreferredLanguage();
|
||||
if (language == Language.SYSTEM) {
|
||||
try {
|
||||
// Try using the system language to determine the language file
|
||||
final String property = System.getProperty("user.language", "en");
|
||||
preferredLanguage = Language.fromCode(property);
|
||||
language = Language.fromCode(property);
|
||||
// Make sure the user knows that we're using the system language for translations
|
||||
LOGGER.info("Using system language ({}) for translations.", preferredLanguage);
|
||||
LOGGER.info("Using system language ({}) for translations.", language);
|
||||
} catch (Exception exception) {
|
||||
LOGGER.warn("Could not use system language for translations.");
|
||||
LOGGER.warn("Using default language ({}) for translations.", preferredLanguage);
|
||||
LOGGER.warn("Using default language ({}) for translations.", language);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Using custom language ({}) for translations.", preferredLanguage);
|
||||
LOGGER.info("Using custom language ({}) for translations.", language);
|
||||
}
|
||||
|
||||
// Load all configurations
|
||||
try {
|
||||
generalConfig.load(getAsset("config", preferredLanguage));
|
||||
messagesConfig.load(getAsset("messages", preferredLanguage));
|
||||
webhookConfig.load(getAsset("webhook", preferredLanguage));
|
||||
generalConfig.load(getAsset("config", language));
|
||||
messagesConfig.load(getAsset("messages", language));
|
||||
webhookConfig.load(getAsset("webhook", language));
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("Error loading configuration", exception);
|
||||
}
|
||||
@@ -155,7 +158,7 @@ public final class SonarConfiguration {
|
||||
|
||||
// General settings
|
||||
logPlayerAddresses = generalConfig.getBoolean("general.log-player-addresses");
|
||||
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), 1, 100);
|
||||
maxOnlinePerIp = clamp(generalConfig.getInt("general.max-online-per-ip"), 0, 99);
|
||||
|
||||
// Attack tracker
|
||||
minPlayersForAttack = clamp(generalConfig.getInt("attack-tracker.min-players-for-attack"), 2, 1024);
|
||||
@@ -165,12 +168,6 @@ public final class SonarConfiguration {
|
||||
|
||||
// Database
|
||||
database.type = Database.Type.valueOf(generalConfig.getString("database.type").toUpperCase());
|
||||
database.filename = generalConfig.getString("database.filename");
|
||||
database.host = generalConfig.getString("database.host");
|
||||
database.port = generalConfig.getInt("database.port");
|
||||
database.name = generalConfig.getString("database.name");
|
||||
database.username = generalConfig.getString("database.username");
|
||||
database.password = generalConfig.getString("database.password");
|
||||
database.maximumAge = clamp(generalConfig.getInt("database.maximum-age"), 1, 365);
|
||||
|
||||
// Queue
|
||||
@@ -189,12 +186,11 @@ public final class SonarConfiguration {
|
||||
}
|
||||
|
||||
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled");
|
||||
verification.gravity.checkCollisions = generalConfig.getBoolean("verification.checks.gravity.check-collisions");
|
||||
verification.gravity.checkCollisions = generalConfig.getBoolean("verification.checks.collision.enabled");
|
||||
verification.gravity.captchaOnFail = generalConfig.getBoolean("verification.checks.gravity.captcha-on-fail");
|
||||
verification.gravity.maxMovementTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-movement-ticks"), 2, 100);
|
||||
verification.gravity.gamemode = Verification.Gravity.Gamemode.valueOf(generalConfig.getString("verification.checks.gravity.gamemode"));
|
||||
|
||||
verification.vehicle.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.vehicle.timing"));
|
||||
verification.vehicle.enabled = generalConfig.getBoolean("verification.checks.vehicle.enabled");
|
||||
|
||||
verification.map.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.map-captcha.timing"));
|
||||
verification.map.scratches = generalConfig.getBoolean("verification.checks.map-captcha.effects.scratches");
|
||||
@@ -227,6 +223,7 @@ public final class SonarConfiguration {
|
||||
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
|
||||
verification.brand.maxLength = generalConfig.getInt("verification.checks.client-brand.max-length");
|
||||
|
||||
verification.gamemode = Verification.Gamemode.valueOf(generalConfig.getString("verification.gamemode"));
|
||||
verification.validNameRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-name-regex"));
|
||||
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
|
||||
verification.maxLoginPackets = clamp(generalConfig.getInt("verification.checks.max-login-packets"), 128, 8192);
|
||||
@@ -241,8 +238,6 @@ public final class SonarConfiguration {
|
||||
verification.rememberTime = clamp(generalConfig.getInt("verification.remember-time"), 0, 86400000);
|
||||
verification.blacklistTime = clamp(generalConfig.getInt("verification.blacklist-time"), 0, 86400000);
|
||||
verification.blacklistThreshold = clamp(generalConfig.getInt("verification.blacklist-threshold"), 0, 100);
|
||||
verification.whitelistedProtocols.clear();
|
||||
verification.whitelistedProtocols.addAll(generalConfig.getIntList("verification.whitelisted-protocols"));
|
||||
verification.blacklistedProtocols.clear();
|
||||
verification.blacklistedProtocols.addAll(generalConfig.getIntList("verification.blacklisted-protocols"));
|
||||
|
||||
@@ -424,25 +419,12 @@ public final class SonarConfiguration {
|
||||
private boolean enabled;
|
||||
private boolean checkCollisions;
|
||||
private boolean captchaOnFail;
|
||||
private Gamemode gamemode;
|
||||
private int maxMovementTicks;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Gamemode {
|
||||
SURVIVAL(0),
|
||||
CREATIVE(1),
|
||||
ADVENTURE(2),
|
||||
// Keep this for backwards compatibility
|
||||
SPECTATOR(2);
|
||||
|
||||
private final int id;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static final class Vehicle {
|
||||
private Timing timing;
|
||||
private boolean enabled;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -452,6 +434,23 @@ public final class SonarConfiguration {
|
||||
private Pattern validRegex;
|
||||
}
|
||||
|
||||
private Gamemode gamemode;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Gamemode {
|
||||
NOT_SET(-1),
|
||||
SURVIVAL(0),
|
||||
CREATIVE(1),
|
||||
ADVENTURE(2);
|
||||
|
||||
private final int id;
|
||||
|
||||
public boolean isSurvivalOrAdventure() {
|
||||
return this == SURVIVAL || this == ADVENTURE;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkGeyser;
|
||||
private boolean logConnections;
|
||||
private boolean logDuringAttack;
|
||||
@@ -466,7 +465,6 @@ public final class SonarConfiguration {
|
||||
private int rememberTime;
|
||||
private int blacklistTime;
|
||||
private int blacklistThreshold;
|
||||
private final Collection<Integer> whitelistedProtocols = new HashSet<>(0);
|
||||
private final Collection<Integer> blacklistedProtocols = new HashSet<>(0);
|
||||
|
||||
private Component tooFastReconnect;
|
||||
@@ -487,22 +485,38 @@ public final class SonarConfiguration {
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Type {
|
||||
MYSQL("jdbc:mysql://%s:%d/%s", new MysqlDatabaseTypeAdapter()),
|
||||
MARIADB("jdbc:mariadb://%s:%d/%s", new MariaDbDatabaseTypeAdapter()),
|
||||
H2("jdbc:h2:file:%s", new H2DatabaseTypeAdapter()),
|
||||
NONE(null, null);
|
||||
MYSQL("MySQL", "jdbc:mysql://%s:%d/%s", new MysqlDatabaseTypeAdapter(),
|
||||
Library.builder()
|
||||
.groupId("com{}mysql")
|
||||
.artifactId("mysql-connector-j")
|
||||
.version("8.4.0")
|
||||
.relocate("com{}mysql", "xyz{}jonesdev{}sonar{}libs{}mysql")
|
||||
.build()),
|
||||
MARIADB("MariaDB", "jdbc:mariadb://%s:%d/%s", new MariaDbDatabaseTypeAdapter(),
|
||||
Library.builder()
|
||||
.groupId("org{}mariadb{}jdbc")
|
||||
.artifactId("mariadb-java-client")
|
||||
.version("3.4.0")
|
||||
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
|
||||
.build()),
|
||||
H2("H2", "jdbc:h2:file:%s", new H2DatabaseTypeAdapter(),
|
||||
Library.builder()
|
||||
.groupId("com{}h2database")
|
||||
.artifactId("h2")
|
||||
.version("2.1.214")
|
||||
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
|
||||
.build()),
|
||||
NONE("None", null, null, null);
|
||||
|
||||
private final String displayName;
|
||||
private final String connectionString;
|
||||
private final DatabaseType databaseType;
|
||||
private final Library databaseDriver;
|
||||
@Setter
|
||||
private boolean downloaded;
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String filename;
|
||||
private String host;
|
||||
private int port;
|
||||
private String name;
|
||||
private String username;
|
||||
private String password;
|
||||
private int maximumAge;
|
||||
}
|
||||
|
||||
|
||||
+48
-23
@@ -17,6 +17,7 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.controller;
|
||||
|
||||
import com.alessiodp.libby.LibraryManager;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
|
||||
@@ -71,7 +72,7 @@ public final class VerifiedPlayerController {
|
||||
@Getter
|
||||
private final @NotNull SonarConfiguration.Database.Type cachedDatabaseType;
|
||||
|
||||
public VerifiedPlayerController() {
|
||||
public VerifiedPlayerController(final @NotNull LibraryManager libraryManager) {
|
||||
final SonarConfiguration.Database database = Sonar.get().getConfig().getDatabase();
|
||||
// Cache selected database type, so we don't need to call Sonar.get() every time
|
||||
cachedDatabaseType = database.getType();
|
||||
@@ -82,22 +83,39 @@ public final class VerifiedPlayerController {
|
||||
return;
|
||||
}
|
||||
|
||||
database.getType().getDatabaseType().loadDriver();
|
||||
// Make sure to only download the driver once
|
||||
if (!cachedDatabaseType.isDownloaded()) {
|
||||
LOGGER.info("Downloading {} driver version {}",
|
||||
cachedDatabaseType.getDatabaseType().getDatabaseName(),
|
||||
cachedDatabaseType.getDatabaseDriver().getVersion());
|
||||
// Download and load the driver for the current database
|
||||
libraryManager.loadLibrary(cachedDatabaseType.getDatabaseDriver());
|
||||
cachedDatabaseType.setDownloaded(true);
|
||||
}
|
||||
// Make sure to actually register the loader, so we can use it
|
||||
cachedDatabaseType.getDatabaseType().loadDriver();
|
||||
|
||||
try {
|
||||
String jdbcURL = String.format(database.getType().getConnectionString(),
|
||||
database.getHost(), database.getPort(), database.getName());
|
||||
final String jdbcURL;
|
||||
|
||||
// H2 has a different JDBC URL layout
|
||||
// https://www.codejava.net/java-se/jdbc/connect-to-h2-database-examples
|
||||
if (cachedDatabaseType == SonarConfiguration.Database.Type.H2) {
|
||||
final File file = new File(Sonar.get().getConfig().getPluginFolder(),
|
||||
Sonar.get().getConfig().getDatabase().getFilename());
|
||||
jdbcURL = String.format(database.getType().getConnectionString(), file.getAbsolutePath());
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("database.filename"));
|
||||
jdbcURL = String.format(cachedDatabaseType.getConnectionString(), file.getAbsolutePath());
|
||||
} else {
|
||||
// Normal JDBC URL layout for MySQL/MariaDB/...
|
||||
jdbcURL = String.format(cachedDatabaseType.getConnectionString(),
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("database.host"),
|
||||
Sonar.get().getConfig().getGeneralConfig().getInt("database.port"),
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("database.name"));
|
||||
}
|
||||
|
||||
connectionSource = new JdbcPooledConnectionSource(jdbcURL,
|
||||
database.getUsername(), database.getPassword(), database.getType().getDatabaseType());
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("database.username"),
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("database.password"),
|
||||
cachedDatabaseType.getDatabaseType());
|
||||
|
||||
// Create database table
|
||||
try {
|
||||
@@ -253,37 +271,44 @@ public final class VerifiedPlayerController {
|
||||
*
|
||||
* @return Estimated size of the local cache
|
||||
*/
|
||||
public synchronized int estimatedSize() {
|
||||
/*
|
||||
* TODO: Work on this
|
||||
* return MAP.values().stream()
|
||||
* .mapToInt(Collection::size)
|
||||
* .sum();
|
||||
*/
|
||||
public int estimatedSize() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of UUIDs associated with one IP address
|
||||
* Returns the sum of verified IP addresses
|
||||
* and their respective collection of UUIDs
|
||||
*
|
||||
* @return Exact size of all total verified players
|
||||
*/
|
||||
@Deprecated
|
||||
public synchronized int exactSize() {
|
||||
return cache.values().stream()
|
||||
.mapToInt(Collection::size)
|
||||
.sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link java.util.Collection} of UUIDs associated with an IP address
|
||||
*/
|
||||
public @Unmodifiable Collection<UUID> getUUIDs(final @NotNull String inetAddress) {
|
||||
return cache.getOrDefault(inetAddress, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the local cache, and then if the database type is set,
|
||||
* Clear the local cache, and, if the database type is set,
|
||||
* remove the table from the database.
|
||||
*/
|
||||
public void clearAll() {
|
||||
try {
|
||||
cache.clear();
|
||||
public synchronized void clearAll() {
|
||||
cache.clear();
|
||||
|
||||
// Only update the column if the database type is not NONE
|
||||
if (cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
|
||||
// Delete the entire table from the database, if necessary
|
||||
if (cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
|
||||
try {
|
||||
dao.deleteBuilder().delete();
|
||||
} catch (SQLException exception) {
|
||||
LOGGER.error("Error trying to clear entries: {}", exception);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
LOGGER.error("Error trying to clear entries: {}", exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -26,7 +25,6 @@ import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class AttackMitigatedEvent implements SonarEvent {
|
||||
private final AttackTracker.AttackStatistics attackStatistics;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -27,7 +26,6 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserBlacklistedEvent implements SonarEvent {
|
||||
@ApiStatus.Experimental
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -27,7 +26,6 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyFailedEvent implements SonarEvent {
|
||||
private final @NotNull FallbackUser user;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -26,7 +25,6 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyJoinEvent implements SonarEvent {
|
||||
private final String username;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -28,7 +27,6 @@ import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifySuccessEvent implements SonarEvent {
|
||||
private final String username;
|
||||
|
||||
@@ -75,10 +75,6 @@ public final class Fallback {
|
||||
return shouldPerform(Sonar.get().getConfig().getVerification().getMap().getTiming());
|
||||
}
|
||||
|
||||
public boolean shouldPerformVehicleCheck() {
|
||||
return shouldPerform(Sonar.get().getConfig().getVerification().getVehicle().getTiming());
|
||||
}
|
||||
|
||||
private static boolean shouldPerform(final SonarConfiguration.Verification.Timing timing) {
|
||||
return timing == SonarConfiguration.Verification.Timing.ALWAYS
|
||||
|| (timing == SonarConfiguration.Verification.Timing.DURING_ATTACK
|
||||
|
||||
@@ -18,8 +18,14 @@
|
||||
package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
public interface FallbackPipelines {
|
||||
String FALLBACK_HANDLER = "sonar-fallback-handler";
|
||||
String FALLBACK_PACKET_ENCODER = "sonar-fallback-encoder";
|
||||
String FALLBACK_PACKET_DECODER = "sonar-fallback-decoder";
|
||||
String FALLBACK_BANDWIDTH = "sonar-fallback-bandwidth";
|
||||
String FALLBACK_INACTIVE_LISTENER = "sonar-inactive-listener";
|
||||
String FALLBACK_INBOUND_HANDLER = "sonar-inbound-handler";
|
||||
String FALLBACK_FRAME_DECODER = "sonar-frame-decoder";
|
||||
String FALLBACK_FRAME_ENCODER = "sonar-frame-encoder";
|
||||
String FALLBACK_TIMEOUT = "sonar-timeout";
|
||||
String FALLBACK_PACKET_HANDLER = "sonar-packet-handler";
|
||||
String FALLBACK_PACKET_ENCODER = "sonar-packet-encoder";
|
||||
String FALLBACK_PACKET_DECODER = "sonar-packet-decoder";
|
||||
String FALLBACK_TAIL_EXCEPTIONS = "sonar-exception-tail";
|
||||
String FALLBACK_BANDWIDTH = "sonar-bandwidth-counter";
|
||||
}
|
||||
@@ -44,6 +44,9 @@ public interface FallbackUser {
|
||||
@NotNull
|
||||
SystemTimer getLoginTimer();
|
||||
|
||||
@NotNull
|
||||
UUID getOfflineUuid();
|
||||
|
||||
boolean isReceivedClientSettings();
|
||||
|
||||
void setReceivedClientSettings(final boolean receivedClientSettings);
|
||||
@@ -65,16 +68,10 @@ public interface FallbackUser {
|
||||
/**
|
||||
* Takes over the channel and begins the verification process
|
||||
*
|
||||
* @param username Username of the player
|
||||
* @param uuid UUID of the player
|
||||
* @param encoder Name of the encoder pipeline
|
||||
* @param decoder Name of the decoder pipeline
|
||||
* @param timeout Name of the read timeout pipeline
|
||||
* @param handler Name of the main pipeline
|
||||
* @param username Username of the player
|
||||
* @param offlineUuid UUID of the player
|
||||
*/
|
||||
void hijack(final @NotNull String username, final @NotNull UUID uuid,
|
||||
final @NotNull String encoder, final @NotNull String decoder,
|
||||
final @NotNull String timeout, final @NotNull String handler);
|
||||
void hijack(final @NotNull String username, final @NotNull UUID offlineUuid);
|
||||
|
||||
/**
|
||||
* Sends a packet/message to the player
|
||||
|
||||
@@ -6,14 +6,13 @@ plugins {
|
||||
|
||||
bukkit {
|
||||
name = rootProject.name
|
||||
description = rootProject.description
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
website = "https://jonesdev.xyz/discord/"
|
||||
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
|
||||
softDepend = listOf("Geyser-Spigot", "floodgate", "Protocolize", "ProtocolSupport",
|
||||
"ViaVersion", "packetevents", "ProtocolLib", "FastLogin")
|
||||
apiVersion = "1.13" // ignore legacy plugin warning
|
||||
foliaSupported = true
|
||||
|
||||
commands {
|
||||
@@ -33,11 +32,11 @@ dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
compileOnly(rootProject.libs.spigot)
|
||||
testCompileOnly(rootProject.libs.spigot)
|
||||
|
||||
implementation(rootProject.libs.adventure.platform.bukkit)
|
||||
implementation(rootProject.libs.adventure.platform.api)
|
||||
implementation(rootProject.libs.adventure.platform.facet)
|
||||
implementation(rootProject.libs.adventure.nbt)
|
||||
implementation(rootProject.libs.adventure.platform.bukkit) {
|
||||
exclude(module = "adventure-nbt")
|
||||
}
|
||||
implementation(rootProject.libs.bstats.bukkit)
|
||||
implementation(rootProject.libs.libby.bukkit)
|
||||
}
|
||||
|
||||
@@ -23,24 +23,24 @@ import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
|
||||
import xyz.jonesdev.sonar.bukkit.listener.BukkitJoinListener;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Getter
|
||||
public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
public static SonarBukkit INSTANCE;
|
||||
|
||||
public SonarBukkit(final @NotNull SonarBukkitPlugin plugin) {
|
||||
super(plugin, SonarPlatform.BUKKIT, plugin.getDataFolder(),
|
||||
new BukkitLibraryManager(plugin, plugin.getDataFolder().getName()));
|
||||
INSTANCE = this;
|
||||
super(plugin, SonarPlatform.BUKKIT, plugin.getDataFolder(), new BukkitLibraryManager(plugin));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +56,11 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
return bukkitAudiences.player(uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Audience sender(final @NotNull Object object) {
|
||||
return bukkitAudiences.sender((CommandSender) object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
@@ -82,6 +87,8 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
|
||||
private Metrics metrics;
|
||||
|
||||
public static final CompletableFuture<Void> INITIALIZE_LISTENER = new CompletableFuture<>();
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
// Initialize bStats.org metrics
|
||||
@@ -92,9 +99,23 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
() -> getConfig().getVerification().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("captcha",
|
||||
() -> getConfig().getVerification().getMap().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("language",
|
||||
() -> getConfig().getLanguage().getName()));
|
||||
metrics.addCustomChart(new SimplePie("database_type",
|
||||
() -> getConfig().getDatabase().getType().getDisplayName()));
|
||||
|
||||
// Register Sonar command
|
||||
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand());
|
||||
|
||||
// Try to inject into the server
|
||||
if (FallbackBukkitInjector.isLateBindEnabled()) {
|
||||
getPlugin().getServer().getScheduler().runTask(getPlugin(), FallbackBukkitInjector::inject);
|
||||
} else {
|
||||
getPlugin().getServer().getPluginManager().registerEvents(new BukkitJoinListener(), getPlugin());
|
||||
}
|
||||
|
||||
// Let the injector know that the plugin has been enabled
|
||||
INITIALIZE_LISTENER.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,10 +18,19 @@
|
||||
package xyz.jonesdev.sonar.bukkit;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
|
||||
|
||||
public final class SonarBukkitPlugin extends JavaPlugin {
|
||||
private SonarBukkit bootstrap;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Inject early if late-bind is disabled
|
||||
if (!FallbackBukkitInjector.isLateBindEnabled()) {
|
||||
FallbackBukkitInjector.inject();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
bootstrap = new SonarBukkit(this);
|
||||
|
||||
+2
-2
@@ -23,9 +23,9 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, S
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new InvocationSource(
|
||||
sender instanceof Player ? ((Player) sender).getUniqueId() : null,
|
||||
SonarBukkit.INSTANCE.getBukkitAudiences().sender(sender),
|
||||
Sonar.get().sender(sender),
|
||||
sender::hasPermission);
|
||||
// Pass the invocation source and command arguments to our command handler
|
||||
handle(invocationSource, args);
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.bukkit.fallback;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum BukkitServerVersion {
|
||||
MINECRAFT_1_7_10,
|
||||
MINECRAFT_1_8,
|
||||
MINECRAFT_1_8_3,
|
||||
MINECRAFT_1_8_8,
|
||||
MINECRAFT_1_9,
|
||||
MINECRAFT_1_9_2,
|
||||
MINECRAFT_1_9_4,
|
||||
MINECRAFT_1_10,
|
||||
MINECRAFT_1_10_1,
|
||||
MINECRAFT_1_10_2,
|
||||
MINECRAFT_1_11,
|
||||
MINECRAFT_1_11_2,
|
||||
MINECRAFT_1_12,
|
||||
MINECRAFT_1_12_1,
|
||||
MINECRAFT_1_12_2,
|
||||
MINECRAFT_1_13,
|
||||
MINECRAFT_1_13_1,
|
||||
MINECRAFT_1_13_2,
|
||||
MINECRAFT_1_14,
|
||||
MINECRAFT_1_14_1,
|
||||
MINECRAFT_1_14_2,
|
||||
MINECRAFT_1_14_3,
|
||||
MINECRAFT_1_14_4,
|
||||
MINECRAFT_1_15,
|
||||
MINECRAFT_1_15_1,
|
||||
MINECRAFT_1_15_2,
|
||||
MINECRAFT_1_16,
|
||||
MINECRAFT_1_16_1,
|
||||
MINECRAFT_1_16_2,
|
||||
MINECRAFT_1_16_3,
|
||||
MINECRAFT_1_16_4,
|
||||
MINECRAFT_1_16_5,
|
||||
MINECRAFT_1_17,
|
||||
MINECRAFT_1_17_1,
|
||||
MINECRAFT_1_18,
|
||||
MINECRAFT_1_18_1,
|
||||
MINECRAFT_1_18_2,
|
||||
MINECRAFT_1_19,
|
||||
MINECRAFT_1_19_1,
|
||||
MINECRAFT_1_19_2,
|
||||
MINECRAFT_1_19_3,
|
||||
MINECRAFT_1_19_4,
|
||||
MINECRAFT_1_20,
|
||||
MINECRAFT_1_20_1,
|
||||
MINECRAFT_1_20_2,
|
||||
MINECRAFT_1_20_3,
|
||||
MINECRAFT_1_20_4,
|
||||
MINECRAFT_1_20_5,
|
||||
MINECRAFT_1_20_6,
|
||||
MINECRAFT_1_21;
|
||||
|
||||
static final BukkitServerVersion[] REVERSED_VALUES;
|
||||
|
||||
static {
|
||||
REVERSED_VALUES = values().clone();
|
||||
|
||||
final int size = REVERSED_VALUES.length;
|
||||
final int half = size / 2;
|
||||
|
||||
for (int i = 0; i < half; i++) {
|
||||
final BukkitServerVersion temp = REVERSED_VALUES[i];
|
||||
REVERSED_VALUES[i] = REVERSED_VALUES[size - 1 - i];
|
||||
REVERSED_VALUES[size - 1 - i] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private final String release;
|
||||
|
||||
BukkitServerVersion() {
|
||||
this.release = name().substring(10).replace("_", ".");
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
package xyz.jonesdev.sonar.bukkit.fallback;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class ChannelInactiveListener extends ChannelInboundHandlerAdapter {
|
||||
private final Set<ChannelInboundHandler> handlers = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
final ProxiedChannelHandlerContext proxy = new ProxiedChannelHandlerContext(ctx);
|
||||
for (final ChannelInboundHandler handler : handlers) {
|
||||
try {
|
||||
handler.channelInactive(proxy);
|
||||
} catch (Exception exception) {
|
||||
handler.exceptionCaught(proxy, exception);
|
||||
}
|
||||
}
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
public void add(ChannelHandler handler) {
|
||||
if (handler instanceof ChannelInboundHandler) {
|
||||
handlers.add((ChannelInboundHandler) handler);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class ProxiedChannelHandlerContext implements ChannelHandlerContext {
|
||||
private final ChannelHandlerContext ctx;
|
||||
|
||||
// Overwrite to prevent call this method on next handler
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelInactive() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireExceptionCaught(Throwable throwable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return ctx.channel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventExecutor executor() {
|
||||
return ctx.executor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ctx.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler handler() {
|
||||
return ctx.handler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemoved() {
|
||||
return ctx.isRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelRegistered() {
|
||||
return ctx.fireChannelRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelUnregistered() {
|
||||
return ctx.fireChannelUnregistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelActive() {
|
||||
return ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireUserEventTriggered(Object o) {
|
||||
return ctx.fireUserEventTriggered(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelRead(Object o) {
|
||||
return ctx.fireChannelRead(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelReadComplete() {
|
||||
return ctx.fireChannelReadComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext fireChannelWritabilityChanged() {
|
||||
return ctx.fireChannelWritabilityChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress socketAddress) {
|
||||
return ctx.bind(socketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress socketAddress) {
|
||||
return ctx.connect(socketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress socketAddress, SocketAddress socketAddress1) {
|
||||
return ctx.connect(socketAddress, socketAddress1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect() {
|
||||
return ctx.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close() {
|
||||
return ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister() {
|
||||
return ctx.deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress socketAddress, ChannelPromise channelPromise) {
|
||||
return ctx.bind(socketAddress, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress socketAddress, ChannelPromise channelPromise) {
|
||||
return ctx.connect(socketAddress, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress socketAddress, SocketAddress socketAddress1, ChannelPromise channelPromise) {
|
||||
return ctx.connect(socketAddress, socketAddress1, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect(ChannelPromise channelPromise) {
|
||||
return ctx.disconnect(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close(ChannelPromise channelPromise) {
|
||||
return ctx.close(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister(ChannelPromise channelPromise) {
|
||||
return ctx.deregister(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext read() {
|
||||
return ctx.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object o) {
|
||||
return ctx.write(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object o, ChannelPromise channelPromise) {
|
||||
return ctx.write(o, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext flush() {
|
||||
return ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object o, ChannelPromise channelPromise) {
|
||||
return ctx.writeAndFlush(o, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object o) {
|
||||
return ctx.writeAndFlush(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise newPromise() {
|
||||
return ctx.newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelProgressivePromise newProgressivePromise() {
|
||||
return ctx.newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newSucceededFuture() {
|
||||
return ctx.newSucceededFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newFailedFuture(Throwable throwable) {
|
||||
return ctx.newFailedFuture(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise voidPromise() {
|
||||
return ctx.voidPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return ctx.pipeline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return ctx.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public <T> Attribute<T> attr(AttributeKey<T> attributeKey) {
|
||||
return ctx.attr(attributeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public <T> boolean hasAttr(AttributeKey<T> attributeKey) {
|
||||
return ctx.hasAttr(attributeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.bukkit.fallback;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacket;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INACTIVE_LISTENER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.*;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
|
||||
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
FallbackBukkitInboundHandler() {
|
||||
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
|
||||
|
||||
channelRemovalListener = (pipeline, name, handler) -> {
|
||||
final var inactiveListener = (ChannelInactiveListener) pipeline.get(FALLBACK_INACTIVE_LISTENER);
|
||||
|
||||
if (inactiveListener != null) {
|
||||
inactiveListener.add(handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
|
||||
final @NotNull ProtocolVersion protocolVersion) {
|
||||
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
if (msg instanceof ByteBuf) {
|
||||
final ByteBuf byteBuf = (ByteBuf) msg;
|
||||
|
||||
// Reset the reader index, so we can read the content
|
||||
// Make sure to store the original reader index
|
||||
final int originalReaderIndex = byteBuf.readerIndex();
|
||||
byteBuf.readerIndex(0);
|
||||
|
||||
// Don't continue if the ByteBuf isn't readable or the channel is closed
|
||||
if (!ctx.channel().isActive() || !byteBuf.isReadable()) {
|
||||
byteBuf.release();
|
||||
return;
|
||||
}
|
||||
|
||||
final int packetId = readVarInt(byteBuf);
|
||||
final FallbackPacket packet = registry.createPacket(packetId);
|
||||
|
||||
// Skip the packet if it's not registered within Sonar's packet registry
|
||||
if (packet == null) {
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to decode the packet for the given protocol version
|
||||
packet.decode(byteBuf, protocolVersion == null ? DEFAULT_PROTOCOL_VERSION : protocolVersion);
|
||||
} catch (Throwable throwable) {
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
throw new CorruptedFrameException("Failed to decode packet");
|
||||
}
|
||||
|
||||
// Check if the packet still has bytes left after we decoded it
|
||||
if (byteBuf.isReadable()) {
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
throw new CorruptedFrameException("Could not read packet to end");
|
||||
}
|
||||
|
||||
// Useful resources:
|
||||
// https://wiki.vg/Protocol#Handshaking
|
||||
// https://wiki.vg/Protocol#Login
|
||||
if (packet instanceof HandshakePacket) {
|
||||
final HandshakePacket handshake = (HandshakePacket) packet;
|
||||
switch (handshake.getIntent()) {
|
||||
case STATUS:
|
||||
// We don't care about server pings or transfers; remove the handler
|
||||
ctx.channel().pipeline().remove(this);
|
||||
break;
|
||||
case LOGIN:
|
||||
case TRANSFER:
|
||||
// Let the actual handler know about the handshake packet
|
||||
handleHandshake(ctx.channel(), handshake.getHostname(), handshake.getProtocolVersionId());
|
||||
// Be ready for the next packet (which is supposed to be a login packet)
|
||||
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
|
||||
break;
|
||||
default:
|
||||
throw new CorruptedFrameException("Unknown handshake intent " + handshake.getIntent());
|
||||
}
|
||||
} else if (packet instanceof LoginStartPacket) {
|
||||
final LoginStartPacket loginStart = (LoginStartPacket) packet;
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.channel().pipeline().remove(this);
|
||||
// Let Sonar process the login packet
|
||||
handleLogin(ctx.channel(), ctx, () -> {
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
ctx.fireChannelRead(byteBuf.retain());
|
||||
final ChannelHandler inboundHandler = ctx.channel().pipeline().remove(FALLBACK_INBOUND_HANDLER);
|
||||
if (inboundHandler != null) {
|
||||
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
}
|
||||
}, loginStart.getUsername(), socketAddress);
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
return;
|
||||
}
|
||||
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
ctx.fireChannelRead(byteBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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.bukkit.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPipelines;
|
||||
import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
|
||||
|
||||
// Check out these links if you want to see some more magic
|
||||
// https://github.com/retrooper/packetevents/blob/2.0/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotReflectionUtil.java
|
||||
// https://github.com/dmulloy2/ProtocolLib/blob/master/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java
|
||||
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java
|
||||
@UtilityClass
|
||||
public class FallbackBukkitInjector {
|
||||
private final String LEGACY_NMS_PACKAGE;
|
||||
private final String OBC_PACKAGE;
|
||||
|
||||
private final BukkitServerVersion SERVER_VERSION;
|
||||
private final boolean USES_LEGACY_PACKAGING, OBFUSCATED;
|
||||
|
||||
private final Class<?> MINECRAFT_SERVER_CLASS;
|
||||
private final Class<?> CRAFTBUKKIT_SERVER_CLASS;
|
||||
private final Class<?> SERVER_CONNECTION_CLASS;
|
||||
private final Object SERVER_INSTANCE;
|
||||
|
||||
@Getter
|
||||
private boolean lateBindEnabled;
|
||||
|
||||
static {
|
||||
SERVER_VERSION = resolveServerVersion();
|
||||
USES_LEGACY_PACKAGING = SERVER_VERSION.compareTo(BukkitServerVersion.MINECRAFT_1_17) < 0;
|
||||
|
||||
try {
|
||||
final String bukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
|
||||
|
||||
String modifiedPackageName;
|
||||
if (SERVER_VERSION.compareTo(BukkitServerVersion.MINECRAFT_1_20_5) >= 0) {
|
||||
// Example: org.bukkit.craftbukkit
|
||||
modifiedPackageName = bukkitPackage.split("\\.")[2];
|
||||
} else {
|
||||
// Example: org.bukkit.craftbukkit.v1_16_R3
|
||||
modifiedPackageName = bukkitPackage.split("\\.")[3];
|
||||
}
|
||||
|
||||
LEGACY_NMS_PACKAGE = "net.minecraft.server." + modifiedPackageName + ".";
|
||||
OBC_PACKAGE = bukkitPackage + ".";
|
||||
|
||||
OBFUSCATED = isObfuscated();
|
||||
// Minecraft classes
|
||||
MINECRAFT_SERVER_CLASS = getNMSClass("server.MinecraftServer", "MinecraftServer");
|
||||
SERVER_CONNECTION_CLASS = getNMSClass(OBFUSCATED ? "server.network.ServerConnection" : "server.network.ServerConnectionListener", "ServerConnection");
|
||||
// CraftBukkit classes
|
||||
CRAFTBUKKIT_SERVER_CLASS = getOBCClass("CraftServer");
|
||||
|
||||
Object minecraftServerInstance;
|
||||
try {
|
||||
// 1.20.5+
|
||||
minecraftServerInstance = getFieldAt(MINECRAFT_SERVER_CLASS, MINECRAFT_SERVER_CLASS, 0).get(null);
|
||||
} catch (Exception exception) {
|
||||
minecraftServerInstance = getFieldAt(CRAFTBUKKIT_SERVER_CLASS, MINECRAFT_SERVER_CLASS, 0).get(Bukkit.getServer());
|
||||
}
|
||||
SERVER_INSTANCE = minecraftServerInstance;
|
||||
|
||||
checkLateBind();
|
||||
} catch (Exception exception) {
|
||||
throw new ReflectiveOperationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLateBind() {
|
||||
try {
|
||||
try {
|
||||
Class.forName("org.bukkit.event.server.ServerLoadEvent");
|
||||
// late-bind does not exist on these versions, ignore this case
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
final Class<?> spigotConfiguration = Class.forName("org.spigotmc.SpigotConfig");
|
||||
final Method initFieldMethod = spigotConfiguration.getDeclaredMethod("lateBind");
|
||||
initFieldMethod.setAccessible(true);
|
||||
initFieldMethod.invoke(null);
|
||||
|
||||
if ((boolean) spigotConfiguration.getField("lateBind").get(null)) {
|
||||
lateBindEnabled = true;
|
||||
}
|
||||
} catch (java.lang.ReflectiveOperationException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull Field getFieldAt(final @NotNull Class<?> clazz,
|
||||
final @NotNull Class<?> type,
|
||||
final int index) {
|
||||
int currentIndex = 0;
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (type.isAssignableFrom(field.getType())) {
|
||||
if (currentIndex++ == index) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clazz.getSuperclass() != null) {
|
||||
return getFieldAt(clazz.getSuperclass(), type, index);
|
||||
}
|
||||
throw new IllegalStateException("Could not find field #" + index + " in " + clazz.getName());
|
||||
}
|
||||
|
||||
public static @NotNull Field findField(final boolean checkSuperClass,
|
||||
final @NotNull Class<?> clazz,
|
||||
final String @NotNull ... types) throws NoSuchFieldException {
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
final String fieldTypeName = field.getType().getSimpleName();
|
||||
for (final String type : types) {
|
||||
if (fieldTypeName.equals(type)) {
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkSuperClass && clazz != Object.class && clazz.getSuperclass() != null) {
|
||||
return findField(true, clazz.getSuperclass(), types);
|
||||
}
|
||||
throw new NoSuchFieldException(types[0]);
|
||||
}
|
||||
|
||||
private @NotNull BukkitServerVersion resolveServerVersion() {
|
||||
final String bukkitVersion = Bukkit.getBukkitVersion();
|
||||
|
||||
for (final BukkitServerVersion serverVersion : BukkitServerVersion.REVERSED_VALUES) {
|
||||
if (bukkitVersion.contains(serverVersion.getRelease())) {
|
||||
return serverVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Throw an error if we cannot find the server version
|
||||
throw new IllegalStateException("Could not find server version " + bukkitVersion);
|
||||
}
|
||||
|
||||
private boolean isObfuscated() {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.network.PlayerConnection");
|
||||
return true;
|
||||
} catch (ClassNotFoundException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Class<?> getNMSClass(final String modern, final String legacy) throws ClassNotFoundException {
|
||||
return Class.forName(USES_LEGACY_PACKAGING ? LEGACY_NMS_PACKAGE + legacy : "net.minecraft." + modern);
|
||||
}
|
||||
|
||||
public @NotNull Class<?> getOBCClass(final String clazz) throws ClassNotFoundException {
|
||||
return Class.forName(OBC_PACKAGE + clazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void inject() {
|
||||
try {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
final Field field = getFieldAt(SERVER_CONNECTION_CLASS, List.class, i);
|
||||
|
||||
// Check if the field has the correct generic type;
|
||||
// We need this field to be List<ChannelFuture>
|
||||
if (!field.getGenericType().getTypeName().contains(ChannelFuture.class.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var list = (List<?>) field.get(getFieldAt(MINECRAFT_SERVER_CLASS, SERVER_CONNECTION_CLASS, 0).get(SERVER_INSTANCE));
|
||||
|
||||
for (final Object object : list) {
|
||||
final ChannelFuture channelFuture = (ChannelFuture) object;
|
||||
final List<String> names = channelFuture.channel().pipeline().names();
|
||||
|
||||
ChannelHandler bootstrap = null;
|
||||
|
||||
for (final String name : names) {
|
||||
try {
|
||||
final ChannelHandler handler = channelFuture.channel().pipeline().get(name);
|
||||
final Field childHandlerField = handler.getClass().getDeclaredField("childHandler");
|
||||
childHandlerField.setAccessible(true);
|
||||
final var childHandler = (ChannelInitializer<?>) childHandlerField.get(handler);
|
||||
|
||||
if (childHandler != null) {
|
||||
bootstrap = handler;
|
||||
}
|
||||
break;
|
||||
} catch (Exception exception) {
|
||||
// Some plugins modify this, so we better skip any exceptions
|
||||
}
|
||||
}
|
||||
|
||||
if (bootstrap == null) {
|
||||
// Default to the first channel future if we couldn't find a childHandler
|
||||
bootstrap = channelFuture.channel().pipeline().first();
|
||||
}
|
||||
|
||||
final Field childHandlerField = bootstrap.getClass().getDeclaredField("childHandler");
|
||||
childHandlerField.setAccessible(true);
|
||||
final var originalInitializer = (ChannelInitializer<Channel>) childHandlerField.get(bootstrap);
|
||||
|
||||
ChannelHandler finalBootstrap = bootstrap;
|
||||
|
||||
// Inject our own channel initializer into the original field
|
||||
// If the plugin is not fully enabled but executes the injection,
|
||||
// put an initializer to close the new connection automatically.
|
||||
if (!SonarBukkit.INITIALIZE_LISTENER.isDone()) {
|
||||
final ChannelInitializer<Channel> initializer = new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) {
|
||||
channel.close();
|
||||
}
|
||||
};
|
||||
childHandlerField.set(finalBootstrap, initializer);
|
||||
}
|
||||
|
||||
SonarBukkit.INITIALIZE_LISTENER.thenAccept(v -> {
|
||||
try {
|
||||
childHandlerField.set(finalBootstrap, new FallbackInjectedChannelInitializer(originalInitializer,
|
||||
pipeline -> {
|
||||
pipeline.addAfter("splitter", FALLBACK_PACKET_DECODER, new FallbackBukkitInboundHandler());
|
||||
pipeline.addFirst(FallbackPipelines.FALLBACK_INACTIVE_LISTENER, new ChannelInactiveListener());
|
||||
}
|
||||
));
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new ReflectiveOperationException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.bukkit.listener;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
|
||||
|
||||
// Taken from
|
||||
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/JoinListener.java
|
||||
public final class BukkitJoinListener implements Listener {
|
||||
private static MethodHandle handleMethod;
|
||||
// ServerGamePacketListenerImpl aka. PlayerConnection
|
||||
private static MethodHandle packetListenerField;
|
||||
private static MethodHandle managerField;
|
||||
private static MethodHandle channelField;
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
static {
|
||||
try {
|
||||
final Method handleMethod = FallbackBukkitInjector.getOBCClass("entity.CraftPlayer")
|
||||
.getDeclaredMethod("getHandle");
|
||||
final Field listenerField = FallbackBukkitInjector.findField(false, handleMethod.getReturnType(),
|
||||
"PlayerConnection", "ServerGamePacketListenerImpl");
|
||||
final Field managerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
|
||||
"NetworkManager", "Connection");
|
||||
final Field channelField = FallbackBukkitInjector.getFieldAt(managerField.getType(), Channel.class, 0);
|
||||
|
||||
BukkitJoinListener.handleMethod = MethodHandles.lookup().unreflect(handleMethod);
|
||||
packetListenerField = MethodHandles.lookup().unreflectGetter(listenerField);
|
||||
BukkitJoinListener.managerField = MethodHandles.lookup().unreflectGetter(managerField);
|
||||
BukkitJoinListener.channelField = MethodHandles.lookup().unreflectGetter(channelField);
|
||||
initialized = true;
|
||||
} catch (Exception exception) {
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PlayerJoinEvent event) {
|
||||
if (!initialized || event.getPlayer().hasMetadata("NPC")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Object handle = handleMethod.invoke(event.getPlayer());
|
||||
final Object listener = packetListenerField.invoke(handle);
|
||||
final Object networkManager = managerField.invoke(listener);
|
||||
final Channel channel = (Channel) channelField.invoke(networkManager);
|
||||
|
||||
// Close the channel if Sonar's handlers are not found when the PlayerJoinEvent is called
|
||||
if (channel != null && channel.isActive() && channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
|
||||
channel.close();
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
Sonar.get().getLogger().warn("Failed to the channel for {}: {}", event.getPlayer().getName(), throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ plugins {
|
||||
|
||||
bungee {
|
||||
name = rootProject.name
|
||||
description = rootProject.description
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
|
||||
author = "Jones Development, Sonar Contributors"
|
||||
softDepends = setOf("Geyser-BungeeCord", "floodgate", "Protocolize", "ViaVersion", "packetevents", "FastLogin")
|
||||
@@ -18,10 +16,9 @@ dependencies {
|
||||
compileOnly(rootProject.libs.bungeecord)
|
||||
testCompileOnly(rootProject.libs.bungeecord)
|
||||
|
||||
implementation(rootProject.libs.adventure.platform.bungee)
|
||||
implementation(rootProject.libs.adventure.platform.api)
|
||||
implementation(rootProject.libs.adventure.platform.facet)
|
||||
implementation(rootProject.libs.adventure.nbt)
|
||||
implementation(rootProject.libs.adventure.platform.bungee) {
|
||||
exclude(module = "adventure-nbt")
|
||||
}
|
||||
implementation(rootProject.libs.bstats.bungee)
|
||||
implementation(rootProject.libs.libby.bungee)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.alessiodp.libby.BungeeLibraryManager;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -35,11 +36,8 @@ import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
public static SonarBungee INSTANCE;
|
||||
|
||||
public SonarBungee(final @NotNull SonarBungeePlugin plugin) {
|
||||
super(plugin, SonarPlatform.BUNGEE, plugin.getDataFolder(), new BungeeLibraryManager(plugin));
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +53,11 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
return bungeeAudiences.player(uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Audience sender(final @NotNull Object object) {
|
||||
return bungeeAudiences.sender((CommandSender) object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
@@ -91,6 +94,10 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
() -> getConfig().getVerification().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("captcha",
|
||||
() -> getConfig().getVerification().getMap().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("language",
|
||||
() -> getConfig().getLanguage().getName()));
|
||||
metrics.addCustomChart(new SimplePie("database_type",
|
||||
() -> getConfig().getDatabase().getType().getDisplayName()));
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
|
||||
|
||||
+2
-2
@@ -22,9 +22,9 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new InvocationSource(
|
||||
sender instanceof ProxiedPlayer ? ((ProxiedPlayer) sender).getUniqueId() : null,
|
||||
SonarBungee.INSTANCE.getBungeeAudiences().sender(sender),
|
||||
Sonar.get().sender(sender),
|
||||
sender::hasPermission);
|
||||
// Pass the invocation source and command arguments to our command handler
|
||||
handle(invocationSource, args);
|
||||
|
||||
+15
-14
@@ -27,19 +27,13 @@ import net.md_5.bungee.protocol.packet.Handshake;
|
||||
import net.md_5.bungee.protocol.packet.LoginRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandlerAdapter;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
|
||||
public final class FallbackBungeeChannelHandler extends FallbackChannelHandlerAdapter {
|
||||
|
||||
public FallbackBungeeChannelHandler(final @NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
private static final MethodHandle CHANNEL_WRAPPER_GETTER;
|
||||
|
||||
@@ -53,18 +47,22 @@ public final class FallbackBungeeChannelHandler extends FallbackChannelHandlerAd
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
// TODO: put this into a separate handler
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
// Intercept any packets processed by BungeeCord
|
||||
if (listenForPackets && msg instanceof PacketWrapper) {
|
||||
if (msg instanceof PacketWrapper) {
|
||||
final PacketWrapper packetWrapper = (PacketWrapper) msg;
|
||||
final DefinedPacket wrappedPacket = packetWrapper.packet;
|
||||
// Don't handle any invalid packets
|
||||
if (wrappedPacket != null) {
|
||||
final Channel channel = ctx.channel();
|
||||
// Intercept any handshake packet by the client
|
||||
if (wrappedPacket instanceof Handshake) {
|
||||
final Handshake handshake = (Handshake) wrappedPacket;
|
||||
handleHandshake(handshake.getHost(), handshake.getProtocolVersion());
|
||||
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
|
||||
// We don't care about server pings; remove the handler
|
||||
if (handshake.getRequestedProtocol() == 1) {
|
||||
ctx.channel().pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
else if (wrappedPacket instanceof LoginRequest) {
|
||||
@@ -78,9 +76,12 @@ public final class FallbackBungeeChannelHandler extends FallbackChannelHandlerAd
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
handleLogin(ctx, msg, loginRequest.getData(), socketAddress,
|
||||
PACKET_ENCODER, PACKET_DECODER, TIMEOUT_HANDLER, BOSS_HANDLER);
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.channel().pipeline().remove(this);
|
||||
// Make sure to mark this packet as released
|
||||
packetWrapper.trySingleRelease();
|
||||
// Let Sonar process the login packet
|
||||
handleLogin(channel, ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
|
||||
return;
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -22,13 +22,13 @@ import io.netty.channel.ChannelInitializer;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.md_5.bungee.netty.PipelineUtils;
|
||||
import sun.misc.Unsafe;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.injection.FallbackInjectedChannelInitializer;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.PACKET_DECODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_HANDLER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_HANDLER;
|
||||
|
||||
@UtilityClass
|
||||
public class FallbackBungeeInjector {
|
||||
@@ -39,9 +39,9 @@ public class FallbackBungeeInjector {
|
||||
|
||||
// Make sure to store the original channel initializer
|
||||
final ChannelInitializer<Channel> originalInitializer = PipelineUtils.SERVER_CHILD;
|
||||
final ChannelInitializer<Channel> fallbackInitializer = new FallbackInjectedChannelInitializer(
|
||||
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_HANDLER,
|
||||
new FallbackBungeeChannelHandler(pipeline.channel())));
|
||||
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
|
||||
originalInitializer, pipeline -> pipeline.addAfter(PACKET_DECODER, FALLBACK_PACKET_HANDLER,
|
||||
new FallbackBungeeInboundHandler()));
|
||||
|
||||
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
unsafeField.setAccessible(true);
|
||||
@@ -53,9 +53,9 @@ public class FallbackBungeeInjector {
|
||||
final long offset = unsafe.staticFieldOffset(childField);
|
||||
|
||||
// Replace the original channel initializer
|
||||
unsafe.putObject(base, offset, fallbackInitializer);
|
||||
unsafe.putObject(base, offset, injectedInitializer);
|
||||
} catch (Exception exception) {
|
||||
throw new ReflectiveOperationException(exception);
|
||||
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,27 +56,6 @@ class LibraryLoader {
|
||||
.artifactId("ormlite-jdbc")
|
||||
.version("6.1")
|
||||
.relocate("com{}j256{}ormlite", "xyz{}jonesdev{}sonar{}libs{}ormlite")
|
||||
.build(),
|
||||
// MySQL driver
|
||||
Library.builder()
|
||||
.groupId("com{}mysql")
|
||||
.artifactId("mysql-connector-j")
|
||||
.version("8.4.0")
|
||||
.relocate("com{}mysql", "xyz{}jonesdev{}sonar{}libs{}mysql")
|
||||
.build(),
|
||||
// MariaDB driver
|
||||
Library.builder()
|
||||
.groupId("org{}mariadb{}jdbc")
|
||||
.artifactId("mariadb-java-client")
|
||||
.version("3.4.0")
|
||||
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
|
||||
.build(),
|
||||
// H2 driver
|
||||
Library.builder()
|
||||
.groupId("com{}h2database")
|
||||
.artifactId("h2")
|
||||
.version("2.1.214")
|
||||
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
|
||||
.build()
|
||||
);
|
||||
|
||||
@@ -94,6 +73,12 @@ class LibraryLoader {
|
||||
.artifactId("adventure-text-serializer-gson")
|
||||
.version("4.17.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build(),
|
||||
Library.builder()
|
||||
.groupId("net{}kyori")
|
||||
.artifactId("adventure-nbt")
|
||||
.version("4.17.0")
|
||||
.relocate("net{}kyori", "xyz{}jonesdev{}sonar{}libs{}kyori")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import xyz.jonesdev.sonar.common.service.SonarServiceManager;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.subcommands.*;
|
||||
import xyz.jonesdev.sonar.common.update.UpdateChecker;
|
||||
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
@@ -55,6 +56,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
private Notification notificationHandler;
|
||||
private SonarConfiguration config;
|
||||
private VerifiedPlayerController verifiedPlayerController;
|
||||
private final LibraryManager libraryManager;
|
||||
private final SonarStatistics statistics;
|
||||
private final SonarPlatform platform;
|
||||
private final SubcommandRegistry subcommandRegistry;
|
||||
@@ -66,6 +68,9 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
final @NotNull LibraryManager libraryManager) {
|
||||
// Load all libraries before anything else
|
||||
LibraryLoader.loadLibraries(libraryManager, platform);
|
||||
this.libraryManager = libraryManager;
|
||||
// Check if Netty is up-to-date
|
||||
ProtocolUtil.checkNettyVersion();
|
||||
// Set the Sonar API
|
||||
SonarSupplier.set(this);
|
||||
// Set the plugin instance
|
||||
@@ -180,7 +185,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
// Close the old connection first
|
||||
verifiedPlayerController.close();
|
||||
}
|
||||
verifiedPlayerController = new VerifiedPlayerController();
|
||||
verifiedPlayerController = new VerifiedPlayerController(libraryManager);
|
||||
}
|
||||
|
||||
public final void shutdown() {
|
||||
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.util.GeyserDetection;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter {
|
||||
protected final Channel channel;
|
||||
protected @Nullable String username;
|
||||
protected InetAddress inetAddress;
|
||||
protected ProtocolVersion protocolVersion;
|
||||
protected boolean listenForPackets = true;
|
||||
|
||||
protected static final Fallback FALLBACK = Sonar.get().getFallback();
|
||||
|
||||
@Override
|
||||
public final void channelActive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
// Increase connections per second for the action bar verbose
|
||||
GlobalSonarStatistics.countConnection();
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void channelInactive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
// The player can disconnect without sending the login packet first
|
||||
// Account for this by checking if the inetAddress has been set yet
|
||||
if (inetAddress != null) {
|
||||
// Remove the IP address from the connected players
|
||||
FALLBACK.getConnected().compute(inetAddress, (key, value) -> null);
|
||||
// Remove the IP address from the queue
|
||||
FALLBACK.getQueue().getPlayers().compute(inetAddress, (key, value) -> null);
|
||||
// Remove this account from the online players or decrement the number of accounts with the same IP
|
||||
FALLBACK.getOnline().compute(inetAddress, (key, value) -> value == null || value <= 1 ? null : value - 1);
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
// We can override the default exceptionCaught method since this handler
|
||||
// will run before the connection knows that there has been an error.
|
||||
// Additionally, this will also run after our custom decoder.
|
||||
@Override
|
||||
public final void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Throwable cause) throws Exception {
|
||||
// Close the channel if we encounter any errors.
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and handles incoming handshake packets
|
||||
*
|
||||
* @param hostname Hostname (server address) sent by the client
|
||||
* @param protocol Protocol version number sent by the client
|
||||
*/
|
||||
protected final void handleHandshake(final @NotNull String hostname, final int protocol) throws Exception {
|
||||
// Check if the hostname is invalid
|
||||
if (hostname.isEmpty()) {
|
||||
throw new CorruptedFrameException("Hostname is empty");
|
||||
}
|
||||
// Check if the player has already sent a handshake packet
|
||||
if (protocolVersion != null) {
|
||||
throw new CorruptedFrameException("Already sent handshake");
|
||||
}
|
||||
// Store the protocol version
|
||||
protocolVersion = ProtocolVersion.fromId(protocol);
|
||||
// Hook the traffic listener
|
||||
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and handles incoming login packets
|
||||
*
|
||||
* @param ctx Forwarded channel handler context
|
||||
* @param loginPacket Login packet sent by the client
|
||||
* @param username Username sent by the client
|
||||
* @param socketAddress Socket address of the client
|
||||
*/
|
||||
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Object loginPacket,
|
||||
final @NotNull String username,
|
||||
final @NotNull InetSocketAddress socketAddress,
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String decoder,
|
||||
final @NotNull String timeout,
|
||||
final @NotNull String handler) throws Exception {
|
||||
// Check if the player has already sent a login packet
|
||||
if (this.username != null) {
|
||||
throw new CorruptedFrameException("Already sent login");
|
||||
}
|
||||
// Connections from unknown protocol versions will be discarded
|
||||
// as this is the safest way of handling unwanted connections
|
||||
if (protocolVersion.isUnknown()) {
|
||||
// Sonar does not support snapshots or Minecraft versions older than 1.7.2
|
||||
throw new CorruptedFrameException("Unknown protocol version");
|
||||
}
|
||||
// Increase joins per second for the action bar verbose
|
||||
GlobalSonarStatistics.countLogin();
|
||||
// Store the username and IP address
|
||||
this.username = username;
|
||||
this.inetAddress = socketAddress.getAddress();
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, blacklisted, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (FALLBACK.getQueue().getPlayers().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (FALLBACK.getConnected().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections if configured
|
||||
final boolean geyser = GeyserDetection.isGeyserConnection(channel, socketAddress);
|
||||
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
customDisconnect(channel, protocolVersion, protocolBlacklisted, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we actually have to verify the player
|
||||
final String offlineUUIDString = "OfflinePlayer:" + username;
|
||||
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress.toString(), offlineUUID)) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is allowed to bypass verification
|
||||
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the connection for further processing
|
||||
FALLBACK.getQueue().getPlayers().compute(inetAddress, (key, value) -> () -> {
|
||||
// Check if the username matches the valid name regex to prevent
|
||||
// UTF-16 names or other types of exploits
|
||||
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
|
||||
.matcher(username).matches()) {
|
||||
customDisconnect(channel, protocolVersion, invalidUsername, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance for the Fallback connection
|
||||
final FallbackUser user = new FallbackUserWrapper(channel, inetAddress, protocolVersion, geyser);
|
||||
// Let the verification handler take over the channel
|
||||
user.hijack(username, offlineUUID, encoder, decoder, timeout, handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the maximum accounts per IP limit check before letting the player join
|
||||
*
|
||||
* @param ctx Forwarded channel handler context
|
||||
* @param loginPacket Login packet sent by the client
|
||||
*/
|
||||
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Object loginPacket,
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String handler) throws Exception {
|
||||
// Increment the number of accounts with the same IP
|
||||
FALLBACK.getOnline().compute(inetAddress, (key, value) -> value == null ? 1 : value + 1);
|
||||
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Skip the maximum online per IP check if it's disabled in the configuration
|
||||
if (maxOnlinePerIp > 0) {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int onlinePerIp = FALLBACK.getOnline().getOrDefault(inetAddress, 0);
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
customDisconnect(channel, protocolVersion, tooManyOnlinePerIP, encoder, handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't listen for further packets
|
||||
// TODO: deject the pipeline without breaking disconnect logic
|
||||
listenForPackets = false;
|
||||
// Let the server know about the login packet
|
||||
ctx.fireChannelRead(loginPacket);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackInboundHandler extends ChannelInboundHandlerAdapter {
|
||||
private final Consumer<ChannelPipeline> sonarPipelineInjector;
|
||||
@Setter
|
||||
@Getter
|
||||
private InetAddress inetAddress;
|
||||
|
||||
@Override
|
||||
public void channelActive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
// Increase connections per second for the action bar verbose
|
||||
GlobalSonarStatistics.countConnection();
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelActive();
|
||||
// Add the packet handler pipeline
|
||||
sonarPipelineInjector.accept(ctx.pipeline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
// The player can disconnect without sending the login packet first
|
||||
// Account for this by checking if the inetAddress has been set yet
|
||||
if (inetAddress != null) {
|
||||
// Remove the IP address from the connected players
|
||||
Sonar.get().getFallback().getConnected().compute(inetAddress, (k, v) -> null);
|
||||
// Remove the IP address from the queue
|
||||
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (k, v) -> null);
|
||||
// Remove this account from the online players or decrement the number of accounts with the same IP
|
||||
Sonar.get().getFallback().getOnline().compute(inetAddress, (k, v) -> v == null || v <= 1 ? null : v - 1);
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
// We can override the default exceptionCaught method since this handler
|
||||
// will run before the connection knows that there has been an error.
|
||||
@Override
|
||||
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Throwable cause) throws Exception {
|
||||
// Close the channel if we encounter any errors.
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.util.GeyserUtil;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.closeWith;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
|
||||
protected @Nullable String username;
|
||||
protected ProtocolVersion protocolVersion;
|
||||
protected RemovalListener channelRemovalListener = RemovalListener.EMPTY;
|
||||
|
||||
protected static final Fallback FALLBACK = Sonar.get().getFallback();
|
||||
|
||||
/**
|
||||
* Validates and handles incoming handshake packets
|
||||
*
|
||||
* @param hostname Hostname (server address) sent by the client
|
||||
* @param protocol Protocol version number sent by the client
|
||||
*/
|
||||
protected final void handleHandshake(final @NotNull Channel channel,
|
||||
final @NotNull String hostname,
|
||||
final int protocol) throws Exception {
|
||||
// Check if the hostname is invalid
|
||||
if (hostname.isEmpty()) {
|
||||
throw new CorruptedFrameException("Hostname is empty");
|
||||
}
|
||||
// Check if the player has already sent a handshake packet
|
||||
if (protocolVersion != null) {
|
||||
throw new CorruptedFrameException("Already sent handshake");
|
||||
}
|
||||
// Store the protocol version
|
||||
protocolVersion = ProtocolVersion.fromId(protocol);
|
||||
// Hook the traffic listener
|
||||
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and handles incoming login packets
|
||||
*
|
||||
* @param channel Forwarded client channel
|
||||
* @param loginPacket Login packet sent by the client
|
||||
* @param username Username sent by the client
|
||||
* @param socketAddress Socket address of the client
|
||||
*/
|
||||
protected final void handleLogin(final @NotNull Channel channel,
|
||||
final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Runnable loginPacket,
|
||||
final @NotNull String username,
|
||||
final @NotNull InetSocketAddress socketAddress) throws Exception {
|
||||
// Check if the player has already sent a login packet
|
||||
if (this.username != null) {
|
||||
throw new CorruptedFrameException("Already sent login");
|
||||
}
|
||||
// Connections from unknown protocol versions will be discarded
|
||||
// as this is the safest way of handling unwanted connections
|
||||
if (protocolVersion.isUnknown()) {
|
||||
// Sonar does not support snapshots or Minecraft versions older than 1.7.2
|
||||
throw new CorruptedFrameException("Unknown protocol version");
|
||||
}
|
||||
// Increase joins per second for the action bar verbose
|
||||
GlobalSonarStatistics.countLogin();
|
||||
// Store the username and IP address
|
||||
this.username = username;
|
||||
final FallbackInboundHandler inboundHandler = channel.pipeline().get(FallbackInboundHandler.class);
|
||||
Objects.requireNonNull(inboundHandler).setInetAddress(socketAddress.getAddress());
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklist().asMap().containsKey(inboundHandler.getInetAddress())) {
|
||||
customDisconnect(channel, protocolVersion, blacklisted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (FALLBACK.getConnected().containsKey(inboundHandler.getInetAddress())) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
customDisconnect(channel, protocolVersion, protocolBlacklisted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
initialLogin(ctx, inboundHandler.getInetAddress(), loginPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections if configured
|
||||
final boolean geyser = GeyserUtil.isGeyserConnection(channel, socketAddress);
|
||||
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
|
||||
initialLogin(ctx, inboundHandler.getInetAddress(), loginPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we actually have to verify the player
|
||||
final String offlineUuidString = "OfflinePlayer:" + username;
|
||||
final UUID offlineUuid = UUID.nameUUIDFromBytes(offlineUuidString.getBytes(StandardCharsets.UTF_8));
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inboundHandler.getInetAddress().toString(), offlineUuid)) {
|
||||
initialLogin(ctx, inboundHandler.getInetAddress(), loginPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!FALLBACK.getRatelimiter().attempt(inboundHandler.getInetAddress())) {
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all other pipelines that could still mess up something
|
||||
rewriteProtocol(ctx, channelRemovalListener);
|
||||
|
||||
// Queue the connection for further processing
|
||||
FALLBACK.getQueue().getPlayers().compute(inboundHandler.getInetAddress(), (inetAddress, runnable) -> {
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (runnable != null) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued);
|
||||
// Remove other instances of this IP address from the queue
|
||||
return null;
|
||||
}
|
||||
|
||||
return () -> {
|
||||
// Check if the username matches the valid name regex to prevent
|
||||
// UTF-16 names or other types of exploits
|
||||
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
|
||||
.matcher(username).matches()) {
|
||||
customDisconnect(channel, protocolVersion, invalidUsername);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance for the Fallback connection
|
||||
final FallbackUser user = new FallbackUserWrapper(
|
||||
channel, inboundHandler.getInetAddress(), protocolVersion, offlineUuid, geyser);
|
||||
// Let the verification handler take over the channel
|
||||
user.hijack(username, offlineUuid);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all pipelines and rewrites them using our custom handlers
|
||||
*/
|
||||
private static void rewriteProtocol(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull RemovalListener removalListener) {
|
||||
for (final Map.Entry<String, ChannelHandler> entry : ctx.pipeline()) {
|
||||
// Don't accidentally remove Sonar's handlers
|
||||
if (entry.getKey().startsWith("sonar")
|
||||
// Don't remove floodgate's pipelines
|
||||
|| entry.getKey().startsWith("geyser")
|
||||
|| entry.getKey().startsWith("floodgate")
|
||||
|| entry.getKey().equals("DefaultChannelPipeline$TailContext#0")) {
|
||||
continue;
|
||||
}
|
||||
ctx.pipeline().remove(entry.getValue());
|
||||
removalListener.accept(ctx.pipeline(), entry.getKey(), entry.getValue());
|
||||
}
|
||||
// Add our custom pipelines
|
||||
ctx.pipeline()
|
||||
.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE)
|
||||
.addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
Sonar.get().getConfig().getVerification().getWriteTimeout(),
|
||||
TimeUnit.MILLISECONDS))
|
||||
.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the maximum accounts per IP limit check before letting the player join
|
||||
*
|
||||
* @param ctx Forwarded client channel context
|
||||
* @param inetAddress Resolved client IP address
|
||||
* @param loginPacket Login packet sent by the client
|
||||
*/
|
||||
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull InetAddress inetAddress,
|
||||
final @NotNull Runnable loginPacket) throws Exception {
|
||||
// Increment the number of accounts with the same IP
|
||||
FALLBACK.getOnline().compute(inetAddress, (k, v) -> v == null ? 1 : v + 1);
|
||||
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Skip the maximum online per IP check if it's disabled in the configuration
|
||||
if (maxOnlinePerIp > 0) {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int onlinePerIp = FALLBACK.getOnline().getOrDefault(inetAddress, 0);
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the server know about the login packet
|
||||
loginPacket.run();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect the player before verification (during login)
|
||||
* by replacing the encoder before running the method.
|
||||
*
|
||||
* @param packet Disconnect packet
|
||||
* @param channel Channel of the player
|
||||
* @param protocolVersion Protocol version of the player
|
||||
*/
|
||||
private void customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet) {
|
||||
if (channel.eventLoop().inEventLoop()) {
|
||||
_customDisconnect(channel, protocolVersion, packet);
|
||||
} else {
|
||||
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet));
|
||||
}
|
||||
}
|
||||
|
||||
private void _customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet) {
|
||||
// Remove the main pipeline to completely take over the channel
|
||||
final String handler = Sonar.get().getPlatform().getHandlerFunction().apply(channel.pipeline());
|
||||
if (channel.pipeline().context(handler) != null) {
|
||||
channel.pipeline().remove(handler);
|
||||
}
|
||||
final String encoder = Sonar.get().getPlatform().getEncoderFunction().apply(channel.pipeline());
|
||||
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
|
||||
// Close the channel if no decoder exists
|
||||
if (currentEncoder != null) {
|
||||
// We don't need to update the encoder if it's already present
|
||||
if (!(currentEncoder instanceof FallbackPacketEncoder)) {
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
}
|
||||
closeWith(channel, protocolVersion, packet);
|
||||
} else {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RemovalListener {
|
||||
void accept(final @NotNull ChannelPipeline pipeline,
|
||||
final @NotNull String name,
|
||||
final @NotNull ChannelHandler handler);
|
||||
|
||||
RemovalListener EMPTY = (pipeline, name, handler) -> {};
|
||||
}
|
||||
}
|
||||
+24
-73
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.common.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.Getter;
|
||||
@@ -34,7 +33,7 @@ import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTailExceptionsHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
@@ -46,9 +45,8 @@ import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_ENCODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.CONFIG;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
|
||||
|
||||
@@ -59,6 +57,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
private final ChannelPipeline pipeline;
|
||||
private final InetAddress inetAddress;
|
||||
private final ProtocolVersion protocolVersion;
|
||||
private final UUID offlineUuid;
|
||||
@Setter
|
||||
private boolean receivedClientSettings;
|
||||
@Setter
|
||||
@@ -69,11 +68,13 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
public FallbackUserWrapper(final @NotNull Channel channel,
|
||||
final @NotNull InetAddress inetAddress,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull UUID offlineUuid,
|
||||
final boolean geyser) {
|
||||
this.channel = channel;
|
||||
this.pipeline = channel.pipeline();
|
||||
this.inetAddress = inetAddress;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.offlineUuid = offlineUuid;
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@@ -85,9 +86,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hijack(final @NotNull String username, final @NotNull UUID uuid,
|
||||
final @NotNull String encoder, final @NotNull String decoder,
|
||||
final @NotNull String timeout, final @NotNull String handler) {
|
||||
public void hijack(final @NotNull String username, final @NotNull UUID offlineUuid) {
|
||||
// The player has joined the verification
|
||||
GlobalSonarStatistics.totalAttemptedVerifications++;
|
||||
|
||||
@@ -107,37 +106,34 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
// Call the VerifyJoinEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(username, this));
|
||||
|
||||
// Mark the player as connected by caching them in a map of verifying players
|
||||
Sonar.get().getFallback().getConnected().compute(inetAddress, (key, value) -> (byte) 0);
|
||||
|
||||
// Run this in the channel's event loop to avoid issues
|
||||
channel.eventLoop().execute(() -> {
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
pipeline.replace(timeout, timeout, new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
Sonar.get().getConfig().getVerification().getWriteTimeout(),
|
||||
TimeUnit.MILLISECONDS));
|
||||
// Make sure the channel is still active
|
||||
if (!channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the player as connected by caching them in a map of verifying players
|
||||
Sonar.get().getFallback().getConnected().compute(inetAddress, (_k, _v) -> (byte) 0);
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
pipeline.replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
// Remove the main pipeline to completely take over the channel
|
||||
if (pipeline.get(handler) != null) {
|
||||
pipeline.remove(handler);
|
||||
}
|
||||
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
write(FallbackPreparer.LOGIN_SUCCESS);
|
||||
write(FallbackPreparer.loginSuccess);
|
||||
|
||||
// The LoginSuccess packet has been sent, now we can change the registry state
|
||||
newEncoder.updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? CONFIG : GAME);
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(protocolVersion);
|
||||
pipeline.replace(decoder, FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
|
||||
pipeline.addLast(FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
|
||||
// Listen for all incoming packets by setting the packet listener
|
||||
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username, uuid));
|
||||
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username));
|
||||
|
||||
// Make sure to catch all exceptions during the verification
|
||||
pipeline.addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,8 +208,8 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull Object msg) {
|
||||
if (channel.isActive()) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0
|
||||
&& protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_7_2) >= 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
|
||||
channel.eventLoop().execute(() -> {
|
||||
channel.config().setAutoRead(false);
|
||||
channel.eventLoop().schedule(() -> {
|
||||
@@ -225,49 +221,4 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the player before verification (during login)
|
||||
* by replacing the encoder before running the method.
|
||||
*
|
||||
* @param packet Disconnect packet
|
||||
* @param encoder Encoder to replace
|
||||
* @param handler Main pipeline to remove
|
||||
* @param channel Channel of the player
|
||||
* @param protocolVersion Protocol version of the player
|
||||
*/
|
||||
public static void customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet,
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String handler) {
|
||||
if (channel.eventLoop().inEventLoop()) {
|
||||
_customDisconnect(channel, protocolVersion, packet, encoder, handler);
|
||||
} else {
|
||||
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet, encoder, handler));
|
||||
}
|
||||
}
|
||||
|
||||
private static void _customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet,
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String boss) {
|
||||
// Remove the main pipeline to completely take over the channel
|
||||
if (channel.pipeline().context(boss) != null) {
|
||||
channel.pipeline().remove(boss);
|
||||
}
|
||||
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
|
||||
// Close the channel if no decoder exists
|
||||
if (currentEncoder != null) {
|
||||
// We don't need to update the encoder if it's already present
|
||||
if (!(currentEncoder instanceof FallbackPacketEncoder)) {
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
}
|
||||
closeWith(channel, protocolVersion, packet);
|
||||
} else {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-4
@@ -15,19 +15,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.injection;
|
||||
package xyz.jonesdev.sonar.common.fallback.netty;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackInjectedChannelInitializer extends ChannelInitializer<Channel> {
|
||||
private static final MethodHandle INIT_CHANNEL_METHOD;
|
||||
@@ -41,8 +48,8 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
|
||||
}
|
||||
}
|
||||
|
||||
private final ChannelInitializer<Channel> originalChannelInitializer;
|
||||
private final Consumer<ChannelPipeline> sonarPipelineInjector;
|
||||
private final @NotNull ChannelInitializer<Channel> originalChannelInitializer;
|
||||
private final @NotNull Consumer<ChannelPipeline> sonarPipelineInjector;
|
||||
|
||||
@Override
|
||||
protected void initChannel(final Channel channel) throws Exception {
|
||||
@@ -55,7 +62,14 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
|
||||
|
||||
// Inject Sonar's channel handler into the pipeline
|
||||
if (channel.isActive()) {
|
||||
sonarPipelineInjector.accept(channel.pipeline());
|
||||
final ChannelHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
|
||||
// We need to be careful on Bukkit, as the encoder can be different
|
||||
if (Sonar.get().getPlatform() == SonarPlatform.BUKKIT) {
|
||||
final String encoder = Sonar.get().getPlatform().getEncoderFunction().apply(channel.pipeline());
|
||||
channel.pipeline().addBefore(encoder, FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
} else {
|
||||
channel.pipeline().addFirst(FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public final class FallbackTailExceptionsHandler extends ChannelDuplexHandler {
|
||||
public static final FallbackTailExceptionsHandler INSTANCE = new FallbackTailExceptionsHandler();
|
||||
|
||||
// We can override the default exceptionCaught method since the server
|
||||
// does not have any other pipelines that could handle this error.
|
||||
// Additionally, this will also run after our custom decoder.
|
||||
@Override
|
||||
public void exceptionCaught(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Throwable cause) throws Exception {
|
||||
// Close the channel if we encounter any errors.
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.netty;
|
||||
|
||||
import io.netty.util.ByteProcessor;
|
||||
import lombok.Getter;
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/VarintByteDecoder.java
|
||||
@Getter
|
||||
final class FallbackVarInt21ByteDecoder implements ByteProcessor {
|
||||
private int readVarInt, bytesRead;
|
||||
private DecodeResult result = DecodeResult.TOO_SHORT;
|
||||
|
||||
@Override
|
||||
public boolean process(byte k) {
|
||||
if (k == 0 && bytesRead == 0) {
|
||||
result = DecodeResult.RUN_OF_ZEROES;
|
||||
return true;
|
||||
}
|
||||
if (result == DecodeResult.RUN_OF_ZEROES) {
|
||||
return false;
|
||||
}
|
||||
readVarInt |= (k & 0x7F) << bytesRead++ * 7;
|
||||
if (bytesRead > 3) {
|
||||
result = DecodeResult.TOO_BIG;
|
||||
return false;
|
||||
}
|
||||
if ((k & 0x80) != 128) {
|
||||
result = DecodeResult.SUCCESS;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public enum DecodeResult {
|
||||
SUCCESS,
|
||||
TOO_SHORT,
|
||||
TOO_BIG,
|
||||
RUN_OF_ZEROES
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java
|
||||
public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(final @NotNull ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) {
|
||||
if (!ctx.channel().isActive()) {
|
||||
in.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final FallbackVarInt21ByteDecoder reader = new FallbackVarInt21ByteDecoder();
|
||||
final int end = in.forEachByte(reader);
|
||||
|
||||
if (end == -1) {
|
||||
// We tried to go beyond the end of the buffer.
|
||||
// This is probably a good sign that the buffer was too short to hold a proper varint.
|
||||
if (reader.getResult() == FallbackVarInt21ByteDecoder.DecodeResult.RUN_OF_ZEROES) {
|
||||
// Special case where the entire packet is just a run of zeroes. We ignore them all.
|
||||
in.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader.getResult() == FallbackVarInt21ByteDecoder.DecodeResult.RUN_OF_ZEROES) {
|
||||
// This will return to the point where the next varint starts
|
||||
in.readerIndex(end);
|
||||
} else if (reader.getResult() == FallbackVarInt21ByteDecoder.DecodeResult.SUCCESS) {
|
||||
final int readVarInt = reader.getReadVarInt();
|
||||
final int bytesRead = reader.getBytesRead();
|
||||
|
||||
if (readVarInt < 0) {
|
||||
in.clear();
|
||||
throw new CorruptedFrameException("Invalid VarInt length " + readVarInt);
|
||||
} else if (readVarInt == 0) {
|
||||
// Skip over the empty packet(s) and ignore it
|
||||
in.readerIndex(end + 1);
|
||||
} else {
|
||||
int minimumRead = bytesRead + readVarInt;
|
||||
if (in.isReadable(minimumRead)) {
|
||||
out.add(in.retainedSlice(end + 1, readVarInt));
|
||||
in.skipBytes(minimumRead);
|
||||
}
|
||||
}
|
||||
} else if (reader.getResult() == FallbackVarInt21ByteDecoder.DecodeResult.TOO_BIG) {
|
||||
in.clear();
|
||||
throw new CorruptedFrameException("Too big VarInt " + reader.getReadVarInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.varIntBytes;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java
|
||||
@ChannelHandler.Sharable
|
||||
public final class FallbackVarInt21FrameEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
public static final FallbackVarInt21FrameEncoder INSTANCE = new FallbackVarInt21FrameEncoder();
|
||||
|
||||
@Override
|
||||
protected void encode(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull ByteBuf msg,
|
||||
final @NotNull ByteBuf out) throws Exception {
|
||||
writeVarInt(out, msg.readableBytes());
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull ByteBuf msg,
|
||||
final boolean preferDirect) throws Exception {
|
||||
final int anticipatedRequiredCapacity = varIntBytes(msg.readableBytes()) + msg.readableBytes();
|
||||
return ctx.alloc().buffer(anticipatedRequiredCapacity);
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -26,7 +26,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.RegistryDataPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledgedPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
@@ -38,6 +40,12 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
|
||||
public enum FallbackPacketRegistry {
|
||||
HANDSHAKE {
|
||||
{
|
||||
serverbound.register(HandshakePacket.class, HandshakePacket::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
{
|
||||
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
|
||||
@@ -45,6 +53,8 @@ public enum FallbackPacketRegistry {
|
||||
clientbound.register(LoginSuccessPacket.class, LoginSuccessPacket::new,
|
||||
map(0x02, MINECRAFT_1_7_2, true));
|
||||
|
||||
serverbound.register(LoginStartPacket.class, LoginStartPacket::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false));
|
||||
serverbound.register(LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
}
|
||||
@@ -124,7 +134,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x1A, MINECRAFT_1_19_4, true),
|
||||
map(0x1B, MINECRAFT_1_20_2, true),
|
||||
map(0x1D, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(SetPlayerPositionRotation.class, SetPlayerPositionRotation::new,
|
||||
clientbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
|
||||
map(0x08, MINECRAFT_1_7_2, true),
|
||||
map(0x2E, MINECRAFT_1_9, true),
|
||||
map(0x2F, MINECRAFT_1_12_1, true),
|
||||
@@ -444,7 +454,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x0D, MINECRAFT_1_19_4, false),
|
||||
map(0x0F, MINECRAFT_1_20_2, false),
|
||||
map(0x12, MINECRAFT_1_21, false));
|
||||
serverbound.register(SetPlayerPositionRotation.class, SetPlayerPositionRotation::new,
|
||||
serverbound.register(SetPlayerPositionRotationPacket.class, SetPlayerPositionRotationPacket::new,
|
||||
map(0x06, MINECRAFT_1_7_2, false),
|
||||
map(0x0D, MINECRAFT_1_9, false),
|
||||
map(0x0F, MINECRAFT_1_12, false),
|
||||
|
||||
+23
-12
@@ -26,20 +26,21 @@ import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.RegistryDataPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
import xyz.jonesdev.sonar.common.util.ComponentHolder;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@UtilityClass
|
||||
public class FallbackPreparer {
|
||||
|
||||
private final SecureRandom RANDOM = new SecureRandom();
|
||||
// LoginSuccess
|
||||
public final FallbackPacket LOGIN_SUCCESS = new FallbackPacketSnapshot(new LoginSuccessPacket(new UUID(1L, 1L), "Sonar"));
|
||||
public FallbackPacket loginSuccess;
|
||||
// Abilities
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new FallbackPacketSnapshot(new PlayerAbilitiesPacket(0x00, 0f, 0f));
|
||||
public final FallbackPacket CAPTCHA_ABILITIES = new FallbackPacketSnapshot(new PlayerAbilitiesPacket(0x02, 0f, 0f));
|
||||
@@ -60,7 +61,7 @@ public class FallbackPreparer {
|
||||
public FallbackPacket enterCodeMessage;
|
||||
public FallbackPacket incorrectCaptcha;
|
||||
// JoinGame
|
||||
public final int PLAYER_ENTITY_ID = ThreadLocalRandom.current().nextInt(10);
|
||||
public final int PLAYER_ENTITY_ID = RANDOM.nextInt(10);
|
||||
public FallbackPacket joinGame;
|
||||
// Update Section Blocks
|
||||
public FallbackPacket updateSectionBlocks;
|
||||
@@ -70,7 +71,7 @@ public class FallbackPreparer {
|
||||
// Default Spawn Position
|
||||
public FallbackPacket defaultSpawnPosition;
|
||||
// Spawn Position
|
||||
public final int TELEPORT_ID = ThreadLocalRandom.current().nextInt();
|
||||
public final int TELEPORT_ID = RANDOM.nextInt();
|
||||
public FallbackPacket spawnPosition;
|
||||
// Transfer packet
|
||||
public static FallbackPacket transferToOrigin;
|
||||
@@ -86,21 +87,23 @@ public class FallbackPreparer {
|
||||
|
||||
// Vehicle
|
||||
public FallbackPacket removeEntities;
|
||||
public FallbackPacket spawnEntity;
|
||||
public static FallbackPacket setPassengers;
|
||||
public static final int VEHICLE_ENTITY_ID = PLAYER_ENTITY_ID + 1;
|
||||
public static final int VEHICLE_ENTITY_ID = PLAYER_ENTITY_ID + 1 + RANDOM.nextInt(10);
|
||||
|
||||
// Collisions
|
||||
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (protocol maximum)
|
||||
public final int SPAWN_X_POSITION = 16 / 2; // middle of the chunk
|
||||
public final int SPAWN_Z_POSITION = 16 / 2; // middle of the chunk
|
||||
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is the maximum Y position allowed
|
||||
public final int IN_AIR_Y_POSITION = 1337;
|
||||
|
||||
// Captcha position
|
||||
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotation(
|
||||
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION, 0f, 90f, 0, false));
|
||||
// CAPTCHA position
|
||||
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
|
||||
SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION, 0, 90, 0, false));
|
||||
|
||||
// Platform
|
||||
public BlockType blockType = BlockType.BARRIER;
|
||||
public BlockType blockType;
|
||||
public int maxMovementTick, dynamicSpawnYPosition;
|
||||
public double[] preparedCachedYMotions;
|
||||
public double maxFallDistance;
|
||||
@@ -117,9 +120,13 @@ public class FallbackPreparer {
|
||||
Sonar.get().getLogger().info("Preloading all registered packets...");
|
||||
FallbackPacketRegistry.values();
|
||||
|
||||
// Prepare LoginSuccess packet
|
||||
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(new UUID(1L, 1L),
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username")));
|
||||
|
||||
// Prepare JoinGame packet
|
||||
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
|
||||
Sonar.get().getConfig().getVerification().getGravity().getGamemode().getId(),
|
||||
Sonar.get().getConfig().getVerification().getGamemode().getId(),
|
||||
0, false, 0,
|
||||
true, false, false,
|
||||
new String[]{"minecraft:overworld"}, "minecraft:overworld"));
|
||||
@@ -139,11 +146,13 @@ public class FallbackPreparer {
|
||||
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + (int) Math.ceil(maxFallDistance);
|
||||
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION));
|
||||
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotation(
|
||||
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotationPacket(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
|
||||
0, -90, TELEPORT_ID, false));
|
||||
|
||||
// Prepare collision platform positions
|
||||
blockType = BlockType.valueOf(Sonar.get().getConfig().getGeneralConfig().getString(
|
||||
"verification.checks.collision.collision-block-type").toUpperCase());
|
||||
final BlockUpdate[] changedBlocks = new BlockUpdate[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
|
||||
|
||||
int index = 0;
|
||||
@@ -180,6 +189,8 @@ public class FallbackPreparer {
|
||||
|
||||
// Prepare packets for the vehicle check
|
||||
removeEntities = new FallbackPacketSnapshot(new RemoveEntitiesPacket(VEHICLE_ENTITY_ID));
|
||||
spawnEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(
|
||||
VEHICLE_ENTITY_ID, EntityType.BOAT, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
|
||||
setPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
|
||||
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|
||||
|
||||
+105
-6
@@ -27,12 +27,111 @@ import java.util.function.Function;
|
||||
@SuppressWarnings("unused")
|
||||
@RequiredArgsConstructor
|
||||
public enum BlockType {
|
||||
STONE(protocolVersion -> 1, 1D),
|
||||
STONE(protocolVersion -> 1, 1),
|
||||
// Useful resources:
|
||||
// - https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/
|
||||
// - https://pokechu22.github.io/Burger/
|
||||
// - https://github.com/ViaVersion/Mappings/tree/main/mappings
|
||||
ENCHANTMENT_TABLE(protocolVersion -> {
|
||||
switch (protocolVersion) {
|
||||
case MINECRAFT_1_7_2:
|
||||
case MINECRAFT_1_7_6:
|
||||
default:
|
||||
// 1.7.2-1.12.2
|
||||
return 116;
|
||||
case MINECRAFT_1_13:
|
||||
case MINECRAFT_1_13_1:
|
||||
return 4612;
|
||||
case MINECRAFT_1_13_2:
|
||||
case MINECRAFT_1_14:
|
||||
case MINECRAFT_1_14_1:
|
||||
case MINECRAFT_1_14_2:
|
||||
case MINECRAFT_1_14_3:
|
||||
return 4613;
|
||||
case MINECRAFT_1_14_4:
|
||||
case MINECRAFT_1_15:
|
||||
case MINECRAFT_1_15_1:
|
||||
case MINECRAFT_1_15_2:
|
||||
case MINECRAFT_1_16:
|
||||
return 5116;
|
||||
case MINECRAFT_1_16_1:
|
||||
return 5132;
|
||||
case MINECRAFT_1_16_2:
|
||||
case MINECRAFT_1_16_3:
|
||||
case MINECRAFT_1_16_4:
|
||||
return 5136;
|
||||
case MINECRAFT_1_17:
|
||||
case MINECRAFT_1_17_1:
|
||||
case MINECRAFT_1_18:
|
||||
case MINECRAFT_1_18_2:
|
||||
return 5333;
|
||||
case MINECRAFT_1_19:
|
||||
case MINECRAFT_1_19_1:
|
||||
return 5719;
|
||||
case MINECRAFT_1_19_3:
|
||||
return 7159;
|
||||
case MINECRAFT_1_19_4:
|
||||
return 7385;
|
||||
case MINECRAFT_1_20:
|
||||
case MINECRAFT_1_20_2:
|
||||
case MINECRAFT_1_20_3:
|
||||
case MINECRAFT_1_20_5:
|
||||
case MINECRAFT_1_21:
|
||||
return 7389;
|
||||
}
|
||||
}, 0.75f),
|
||||
TRAPDOOR(protocolVersion -> {
|
||||
switch (protocolVersion) {
|
||||
case MINECRAFT_1_7_2:
|
||||
case MINECRAFT_1_7_6:
|
||||
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
|
||||
return 96;
|
||||
default:
|
||||
// 1.8-1.12.2
|
||||
return 167;
|
||||
case MINECRAFT_1_13:
|
||||
case MINECRAFT_1_13_1:
|
||||
return 6509;
|
||||
case MINECRAFT_1_13_2:
|
||||
case MINECRAFT_1_14:
|
||||
case MINECRAFT_1_14_1:
|
||||
case MINECRAFT_1_14_2:
|
||||
case MINECRAFT_1_14_3:
|
||||
return 6510;
|
||||
case MINECRAFT_1_14_4:
|
||||
case MINECRAFT_1_15:
|
||||
case MINECRAFT_1_15_1:
|
||||
case MINECRAFT_1_15_2:
|
||||
case MINECRAFT_1_16:
|
||||
return 7016;
|
||||
case MINECRAFT_1_16_1:
|
||||
return 7552;
|
||||
case MINECRAFT_1_16_2:
|
||||
case MINECRAFT_1_16_3:
|
||||
case MINECRAFT_1_16_4:
|
||||
return 7556;
|
||||
case MINECRAFT_1_17:
|
||||
case MINECRAFT_1_17_1:
|
||||
case MINECRAFT_1_18:
|
||||
case MINECRAFT_1_18_2:
|
||||
return 7802;
|
||||
case MINECRAFT_1_19:
|
||||
case MINECRAFT_1_19_1:
|
||||
return 8293;
|
||||
case MINECRAFT_1_19_3:
|
||||
return 9937;
|
||||
case MINECRAFT_1_19_4:
|
||||
return 10269;
|
||||
case MINECRAFT_1_20:
|
||||
return 10273;
|
||||
case MINECRAFT_1_20_2:
|
||||
case MINECRAFT_1_20_3:
|
||||
case MINECRAFT_1_20_5:
|
||||
case MINECRAFT_1_21:
|
||||
return 10414;
|
||||
}
|
||||
}, 0.1875),
|
||||
BARRIER(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) {
|
||||
case MINECRAFT_1_7_2:
|
||||
case MINECRAFT_1_7_6:
|
||||
@@ -82,7 +181,7 @@ public enum BlockType {
|
||||
case MINECRAFT_1_21:
|
||||
return 10366;
|
||||
}
|
||||
}, 1D);
|
||||
}, 1);
|
||||
|
||||
private final Function<ProtocolVersion, Integer> idFunction;
|
||||
// TODO: Implement per-version block heights?
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
|
||||
@Getter
|
||||
@ToString(of = {"protocolVersionId", "hostname", "port"})
|
||||
public final class HandshakePacket implements FallbackPacket {
|
||||
private int protocolVersionId;
|
||||
private String hostname;
|
||||
private int port;
|
||||
private int intent;
|
||||
|
||||
private static final String FORGE_TOKEN = "\0FML\0";
|
||||
private static final int MAXIMUM_HOSTNAME_LENGTH = 255 + FORGE_TOKEN.length() + 1;
|
||||
|
||||
// https://wiki.vg/Protocol#Handshaking
|
||||
public static final int STATUS = 1;
|
||||
public static final int LOGIN = 2;
|
||||
public static final int TRANSFER = 3;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
protocolVersionId = readVarInt(byteBuf);
|
||||
hostname = readString(byteBuf, MAXIMUM_HOSTNAME_LENGTH);
|
||||
port = byteBuf.readUnsignedShort();
|
||||
intent = readVarInt(byteBuf);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.login;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class LoginStartPacket implements FallbackPacket {
|
||||
private String username;
|
||||
private @Nullable UUID uuid;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
username = readString(byteBuf, 16);
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||
if (byteBuf.readBoolean()) {
|
||||
readPlayerKey(byteBuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
|
||||
uuid = readUUID(byteBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||
if (byteBuf.readBoolean()) {
|
||||
uuid = readUUID(byteBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void readPlayerKey(final @NotNull ByteBuf byteBuf) {
|
||||
final long expiry = byteBuf.readLong();
|
||||
final byte[] key = readByteArray(byteBuf);
|
||||
final byte[] signature = readByteArray(byteBuf, 4096);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -34,7 +34,7 @@ public final class PaddleBoatPacket implements FallbackPacket {
|
||||
private boolean leftPaddle, rightPaddle;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
+4
-9
@@ -33,7 +33,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class SetPlayerPositionRotation implements FallbackPacket {
|
||||
public final class SetPlayerPositionRotationPacket implements FallbackPacket {
|
||||
private double x, y, z;
|
||||
private float yaw, pitch;
|
||||
private int teleportId;
|
||||
@@ -46,15 +46,10 @@ public final class SetPlayerPositionRotation implements FallbackPacket {
|
||||
byteBuf.writeDouble(z);
|
||||
byteBuf.writeFloat(yaw);
|
||||
byteBuf.writeFloat(pitch);
|
||||
byteBuf.writeBoolean(onGround);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeBoolean(onGround);
|
||||
} else {
|
||||
byteBuf.writeByte(0x00);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
+9
-12
@@ -25,6 +25,7 @@ import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -37,23 +38,24 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class SpawnEntityPacket implements FallbackPacket {
|
||||
private int entityId, entityType;
|
||||
private int entityId;
|
||||
private EntityType entityType;
|
||||
private double x, y, z;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
|
||||
writeVarInt(byteBuf, entityId);
|
||||
|
||||
final boolean v1_9orHigher = protocolVersion.compareTo(MINECRAFT_1_8) > 0;
|
||||
final boolean v1_9orHigher = protocolVersion.compareTo(MINECRAFT_1_9) >= 0;
|
||||
|
||||
if (v1_9orHigher) {
|
||||
writeUUID(byteBuf, UUID.randomUUID());
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
|
||||
writeVarInt(byteBuf, entityType);
|
||||
writeVarInt(byteBuf, entityType.getId(protocolVersion));
|
||||
} else {
|
||||
byteBuf.writeByte(entityType);
|
||||
byteBuf.writeByte(entityType.getId(protocolVersion));
|
||||
}
|
||||
|
||||
if (v1_9orHigher) {
|
||||
@@ -61,9 +63,9 @@ public final class SpawnEntityPacket implements FallbackPacket {
|
||||
byteBuf.writeDouble(y);
|
||||
byteBuf.writeDouble(z);
|
||||
} else {
|
||||
byteBuf.writeInt(floor(x * 32D));
|
||||
byteBuf.writeInt(floor(y * 32D));
|
||||
byteBuf.writeInt(floor(z * 32D));
|
||||
byteBuf.writeInt((int) (x * 32D));
|
||||
byteBuf.writeInt((int) (y * 32D));
|
||||
byteBuf.writeInt((int) (z * 32D));
|
||||
}
|
||||
|
||||
byteBuf.writeByte(0); // pitch or yaw
|
||||
@@ -83,11 +85,6 @@ public final class SpawnEntityPacket implements FallbackPacket {
|
||||
}
|
||||
}
|
||||
|
||||
private static int floor(final double value) {
|
||||
final int __value = (int) value;
|
||||
return value < (double) __value ? __value - 1 : __value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
+29
-23
@@ -17,6 +17,7 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.session;
|
||||
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
@@ -25,8 +26,6 @@ import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.MapCaptchaInfo;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
/**
|
||||
@@ -44,13 +43,20 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
*/
|
||||
public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackCAPTCHASessionHandler(final FallbackUser user, final String username, final UUID uuid) {
|
||||
super(user, username, uuid);
|
||||
public FallbackCAPTCHASessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username) {
|
||||
super(user, username);
|
||||
|
||||
// Disconnect the player if there is no CAPTCHA available at the moment
|
||||
if (!MAP_INFO_PREPARER.isCaptchaAvailable()) {
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
|
||||
throw new CorruptedFrameException("No captcha available");
|
||||
}
|
||||
|
||||
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
|
||||
|
||||
// If the player is on Java, set the 5th slot (ID 4) in the player's hotbar to the map.
|
||||
// If the player is on Bedrock, set the 1st slot (ID 0) in the player's hotbar to the map.
|
||||
// If the player is on Java, set the 5th slot (ID 4) in the player's hotbar to the map
|
||||
// If the player is on Bedrock, set the 1st slot (ID 0) in the player's hotbar to the map
|
||||
user.delayedWrite(new SetContainerSlotPacket(user.isGeyser() ? 36 : 40, 1,
|
||||
ItemType.FILLED_MAP.getId(user.getProtocolVersion()), SetContainerSlotPacket.MAP_NBT));
|
||||
// Send random captcha to the player
|
||||
@@ -61,7 +67,7 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
user.delayedWrite(CAPTCHA_POSITION);
|
||||
// Make sure the player cannot move
|
||||
user.delayedWrite(user.isGeyser() ? CAPTCHA_ABILITIES_BEDROCK : CAPTCHA_ABILITIES);
|
||||
// Make sure the player knows what to do
|
||||
// Make sure the player knows that they have to enter the code in chat
|
||||
user.delayedWrite(enterCodeMessage);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
@@ -72,34 +78,34 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
// Check if the player took too long to enter the captcha
|
||||
// Check if the player took too long to enter the CAPTCHA
|
||||
final int maxDuration = Sonar.get().getConfig().getVerification().getMap().getMaxDuration();
|
||||
checkState(!user.getLoginTimer().elapsed(maxDuration), "took too long to enter captcha");
|
||||
checkState(!user.getLoginTimer().elapsed(maxDuration), "took too long to enter CAPTCHA");
|
||||
|
||||
// Handle incoming chat messages
|
||||
if (packet instanceof SystemChatPacket) {
|
||||
final SystemChatPacket chat = (SystemChatPacket) packet;
|
||||
|
||||
// Captcha is correct, finish verification
|
||||
// Finish the verification if the player entered the correct code
|
||||
if (chat.getMessage().equals(answer)) {
|
||||
finishVerification();
|
||||
return;
|
||||
}
|
||||
|
||||
// Captcha is incorrect, remove one try
|
||||
checkState(tries-- > 0, "failed captcha too often");
|
||||
// Decrement the number of tries left
|
||||
checkState(tries-- > 0, "failed CAPTCHA too often");
|
||||
// Send the player a chat message to let them know that the code they entered is incorrect
|
||||
user.write(incorrectCaptcha);
|
||||
} else if (packet instanceof SetPlayerPositionPacket
|
||||
|| packet instanceof SetPlayerPositionRotation) {
|
||||
|| packet instanceof SetPlayerPositionRotationPacket) {
|
||||
// A position packet is sent approximately every second
|
||||
final long difference = maxDuration - user.getLoginTimer().delay();
|
||||
final int index = (int) (difference / 1000D);
|
||||
// Make sure we can actually safely get and send the packet
|
||||
if (lastCountdownIndex != index && index >= 0 && xpCountdown.length > index) {
|
||||
// Send the countdown using the experience bar
|
||||
user.write(xpCountdown[index]);
|
||||
if (Sonar.get().getConfig().getVerification().getGamemode().isSurvivalOrAdventure()) {
|
||||
final long difference = maxDuration - user.getLoginTimer().delay();
|
||||
final int index = (int) (difference / 1000D);
|
||||
// Make sure we can actually safely get and send the packet
|
||||
if (lastCountdownIndex != index && index >= 0 && xpCountdown.length > index) {
|
||||
// Send the countdown using the experience bar
|
||||
user.write(xpCountdown[index]);
|
||||
}
|
||||
lastCountdownIndex = index;
|
||||
}
|
||||
lastCountdownIndex = index;
|
||||
// Send a KeepAlive packet every few seconds
|
||||
if (keepAliveStreak++ > 20) {
|
||||
keepAliveStreak = 0;
|
||||
|
||||
+20
-12
@@ -24,18 +24,15 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.config.SonarConfiguration.Verification.Gravity.Gamemode.CREATIVE;
|
||||
import static xyz.jonesdev.sonar.api.config.SonarConfiguration.Verification.Gamemode.CREATIVE;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackGravitySessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackGravitySessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
super(user, username, uuid);
|
||||
final @NotNull String username) {
|
||||
super(user, username);
|
||||
|
||||
// We don't want to check Geyser players for valid gravity, as this might cause issues because of the protocol
|
||||
this.enableGravityCheck = !user.isGeyser() && Sonar.get().getConfig().getVerification().getGravity().isEnabled();
|
||||
@@ -46,7 +43,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
user.delayedWrite(joinGame);
|
||||
// Then, write the ClientAbilities packet to the buffer
|
||||
// This is only necessary if the player is in creative mode
|
||||
if (Sonar.get().getConfig().getVerification().getGravity().getGamemode() == CREATIVE) {
|
||||
if (Sonar.get().getConfig().getVerification().getGamemode() == CREATIVE) {
|
||||
user.delayedWrite(DEFAULT_ABILITIES);
|
||||
}
|
||||
// Write the PositionLook packet to the buffer
|
||||
@@ -102,15 +99,15 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
|
||||
// Send the player to the protocol check
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(new FallbackProtocolSessionHandler(user, username, uuid, forceCAPTCHA));
|
||||
decoder.setListener(new FallbackProtocolSessionHandler(user, username, forceCAPTCHA));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
if (packet instanceof SetPlayerPositionRotation) {
|
||||
if (packet instanceof SetPlayerPositionRotationPacket) {
|
||||
// Make sure the player has teleported before checking for position packets
|
||||
if (teleported) {
|
||||
final SetPlayerPositionRotation positionLook = (SetPlayerPositionRotation) packet;
|
||||
final SetPlayerPositionRotationPacket positionLook = (SetPlayerPositionRotationPacket) packet;
|
||||
handleMovement(positionLook.getX(), positionLook.getY(), positionLook.getZ(), positionLook.isOnGround());
|
||||
}
|
||||
} else if (packet instanceof SetPlayerPositionPacket) {
|
||||
@@ -191,6 +188,10 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
movementTick++;
|
||||
|
||||
if (enableGravityCheck) {
|
||||
// Ensure that the player is above the collision platform
|
||||
checkState(y > DEFAULT_Y_COLLIDE_POSITION,
|
||||
"fell through blocks; y: " + y + " deltaY: " + deltaY + " tick: " + movementTick);
|
||||
|
||||
// Predict the player's current motion based on the last motion
|
||||
// https://minecraft.wiki/w/Entity#Motion_of_entities
|
||||
final double predicted = (lastDeltaY - 0.08) * 0.98f;
|
||||
@@ -220,7 +221,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
markSuccess(true);
|
||||
return;
|
||||
}
|
||||
user.fail("illegal collision tick: " + movementTick);
|
||||
user.fail("illegal collision tick; tick: " + movementTick);
|
||||
}
|
||||
// Calculate the difference between the player's Y coordinate and the expected Y coordinate
|
||||
double collisionOffsetY = (DEFAULT_Y_COLLIDE_POSITION + blockType.getBlockHeight()) - y;
|
||||
@@ -230,7 +231,14 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
collisionOffsetY += 1.62f;
|
||||
}
|
||||
// Make sure the player is actually colliding with the blocks and not only spoofing ground
|
||||
checkState(collisionOffsetY > -0.03, "illegal collision: " + collisionOffsetY);
|
||||
if (collisionOffsetY < -0.03) {
|
||||
// Do not throw an exception if the user configured to display the CAPTCHA instead
|
||||
if (Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
|
||||
markSuccess(true);
|
||||
return;
|
||||
}
|
||||
user.fail("illegal collision; offset: " + collisionOffsetY + " tick: " + movementTick + " y: " + y);
|
||||
}
|
||||
// The player has collided with the blocks, go on to the next stage
|
||||
markSuccess(false);
|
||||
}
|
||||
|
||||
+3
-5
@@ -29,7 +29,6 @@ import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientInformatio
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.KeepAlivePacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PluginMessagePacket;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
@@ -61,9 +60,8 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackLoginSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
super(user, username, uuid);
|
||||
final @NotNull String username) {
|
||||
super(user, username);
|
||||
|
||||
// Start initializing the actual join process for pre-1.20.2 clients
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
|
||||
@@ -95,7 +93,7 @@ public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
// Make sure we can actually switch over to the next check
|
||||
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
|
||||
// Pass the player to the next verification handler
|
||||
final FallbackGravitySessionHandler gravitySessionHandler = new FallbackGravitySessionHandler(user, username, uuid);
|
||||
final FallbackGravitySessionHandler gravitySessionHandler = new FallbackGravitySessionHandler(user, username);
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(gravitySessionHandler);
|
||||
}
|
||||
|
||||
+4
-7
@@ -25,15 +25,12 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetHeldItemPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.TransactionPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class FallbackProtocolSessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackProtocolSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid,
|
||||
final boolean forceCAPTCHA) {
|
||||
super(user, username, uuid);
|
||||
super(user, username);
|
||||
|
||||
this.forceCAPTCHA = forceCAPTCHA;
|
||||
|
||||
@@ -80,10 +77,10 @@ public final class FallbackProtocolSessionHandler extends FallbackSessionHandler
|
||||
// send the player to the CAPTCHA, or finish the verification.
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getFallback().shouldPerformVehicleCheck()) {
|
||||
decoder.setListener(new FallbackVehicleSessionHandler(user, username, uuid, forceCAPTCHA));
|
||||
if (Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
|
||||
decoder.setListener(new FallbackVehicleSessionHandler(user, username, forceCAPTCHA));
|
||||
} else if (forceCAPTCHA || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username, uuid));
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username));
|
||||
} else {
|
||||
// The player has passed all checks
|
||||
finishVerification();
|
||||
|
||||
+7
-5
@@ -30,7 +30,6 @@ import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
@@ -43,7 +42,6 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL_LEGACY;
|
||||
public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
protected final FallbackUser user;
|
||||
protected final String username;
|
||||
protected final UUID uuid;
|
||||
|
||||
protected static final Random RANDOM = new Random();
|
||||
|
||||
@@ -61,18 +59,22 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
|
||||
// Add verified player to the database
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(
|
||||
user.getInetAddress(), uuid, user.getLoginTimer().getStart()));
|
||||
user.getInetAddress(), user.getOfflineUuid(), user.getLoginTimer().getStart()));
|
||||
|
||||
// Call the VerifySuccessEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(
|
||||
username, uuid, user, user.getLoginTimer().delay()));
|
||||
username, user.getOfflineUuid(), user, user.getLoginTimer().delay()));
|
||||
|
||||
// If enabled, transfer the player back to the origin server.
|
||||
// This feature was introduced by Mojang in Minecraft version 1.20.5.
|
||||
if (transferToOrigin != null
|
||||
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
|
||||
// Send the transfer packet to the player and close the channel
|
||||
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
|
||||
if (user.isGeyser()) {
|
||||
user.write(transferToOrigin);
|
||||
} else {
|
||||
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
|
||||
}
|
||||
} else {
|
||||
// Disconnect player with the verification success message
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
|
||||
|
||||
+57
-29
@@ -22,14 +22,9 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.entity.EntityType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PaddleBoatPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PlayerInputPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetPassengersPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SpawnEntityPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_9;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
@@ -40,54 +35,83 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
* {@link SpawnEntityPacket} and {@link SetPassengersPacket} packets are sent to the client,
|
||||
* therefore, making the player enter a boat.
|
||||
* <br>
|
||||
* The boat will be spawned at y 1337.
|
||||
* <br>
|
||||
* After 3 {@link PlayerInputPacket} and {@link PaddleBoatPacket} packets,
|
||||
* the boat is removed.
|
||||
* <br>
|
||||
* Now we can check if the player sends the correct position after the entity is removed.
|
||||
* Please note that we don't tell the client to exit the vehicle, therefore, possibly confusing some bots.
|
||||
* Removing the boat results in the player exiting the vehicle on vanilla clients.
|
||||
* <br>
|
||||
* See more: {@link FallbackVehicleSessionHandler}
|
||||
* </li>
|
||||
* <li>
|
||||
* Then, all we do is listen for incoming {@link PlayerInputPacket} and {@link PaddleBoatPacket} packets.
|
||||
* Then, we listen for incoming {@link PlayerInputPacket} and {@link PaddleBoatPacket} packets.
|
||||
* </li>
|
||||
*/
|
||||
public final class FallbackVehicleSessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackVehicleSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid,
|
||||
final boolean forceCAPTCHA) {
|
||||
super(user, username, uuid);
|
||||
super(user, username);
|
||||
|
||||
this.forceCAPTCHA = forceCAPTCHA;
|
||||
|
||||
// Send the necessary packets to mount the player on the vehicle
|
||||
final int entityTypeId = EntityType.BOAT.getId(user.getProtocolVersion());
|
||||
user.delayedWrite(new SpawnEntityPacket(VEHICLE_ENTITY_ID, entityTypeId,
|
||||
SPAWN_X_POSITION, DEFAULT_Y_COLLIDE_POSITION, SPAWN_Z_POSITION));
|
||||
user.delayedWrite(spawnEntity);
|
||||
user.delayedWrite(setPassengers);
|
||||
user.getChannel().flush();
|
||||
}
|
||||
|
||||
private final boolean forceCAPTCHA;
|
||||
private boolean receivedPaddle, receivedInput;
|
||||
private int paddlePackets, inputPackets, positionPackets;
|
||||
private boolean expectMovement;
|
||||
|
||||
private static final int MINIMUM_REQUIRED_PACKETS = 2;
|
||||
|
||||
private void markSuccess() {
|
||||
// Pass the player to the next best verification handler
|
||||
if (forceCAPTCHA || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
// Make sure the player exits the vehicle before sending the CAPTCHA
|
||||
user.delayedWrite(removeEntities);
|
||||
// Either send the player to the CAPTCHA, or finish the verification.
|
||||
// Either send the player to the CAPTCHA or finish the verification.
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
// Send the player to the CAPTCHA handler
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username, uuid));
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username));
|
||||
} else {
|
||||
// The player has passed all checks
|
||||
finishVerification();
|
||||
}
|
||||
}
|
||||
|
||||
// We can abuse the entity remove mechanic and check for position packets when the entity dies
|
||||
private void move(double y) {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
y -= 1.62f; // Account for 1.7 bounding box
|
||||
}
|
||||
// Check the Y position of the player
|
||||
checkState(y <= IN_AIR_Y_POSITION, "invalid y position");
|
||||
// Mark this check as successful if the player sent a few position packets
|
||||
if (positionPackets++ > MINIMUM_REQUIRED_PACKETS) {
|
||||
markSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
if (packet instanceof PaddleBoatPacket) {
|
||||
// Mark the PaddleBoat packet as received
|
||||
receivedPaddle = true;
|
||||
} else if (packet instanceof PlayerInputPacket) {
|
||||
if (packet instanceof SetPlayerPositionRotationPacket) {
|
||||
if (expectMovement) {
|
||||
final SetPlayerPositionRotationPacket posRot = (SetPlayerPositionRotationPacket) packet;
|
||||
move(posRot.getY());
|
||||
}
|
||||
} else if (packet instanceof SetPlayerPositionPacket) {
|
||||
if (expectMovement) {
|
||||
final SetPlayerPositionPacket position = (SetPlayerPositionPacket) packet;
|
||||
move(position.getY());
|
||||
}
|
||||
} else if (packet instanceof PaddleBoatPacket) {
|
||||
paddlePackets++;
|
||||
} else if (packet instanceof PlayerInputPacket && !expectMovement) {
|
||||
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
|
||||
|
||||
// Check if the player is sending invalid vehicle speed values
|
||||
@@ -99,19 +123,23 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this check as success if the player has sent
|
||||
// two PlayerInput and a minimum of one PaddleBoat packets.
|
||||
if (receivedInput && receivedPaddle) {
|
||||
markSuccess();
|
||||
// Once the player sent enough packets, go to the next stage
|
||||
if (paddlePackets > MINIMUM_REQUIRED_PACKETS && inputPackets > MINIMUM_REQUIRED_PACKETS) {
|
||||
// Remove the entity
|
||||
// The next y coordinate the player will send is going
|
||||
// to be the vehicle spawn position (y 64 in this case).
|
||||
user.write(removeEntities);
|
||||
// Listen for next movement packet(s)
|
||||
expectMovement = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.8 and below do not have PaddleBoat packets,
|
||||
// so we simply exempt them from the PaddleBoat check.
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
|
||||
receivedPaddle = true;
|
||||
paddlePackets++;
|
||||
}
|
||||
// Mark the PlayerInput packet as received
|
||||
receivedInput = true;
|
||||
inputPackets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -63,7 +63,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add-warning"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
|
||||
+8
-4
@@ -79,16 +79,18 @@ public final class VerifiedCommand extends Subcommand {
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
// An InetAddress starts with /, we need to account for this
|
||||
final String inetAddress = "/" + rawAddress;
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (!Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
if (!Sonar.get().getVerifiedPlayerController().has(inetAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
Sonar.get().getVerifiedPlayerController().remove(rawAddress);
|
||||
Sonar.get().getVerifiedPlayerController().remove(inetAddress);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
@@ -105,6 +107,8 @@ public final class VerifiedCommand extends Subcommand {
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
// An InetAddress starts with /, we need to account for this
|
||||
final String inetAddress = "/" + rawAddress;
|
||||
|
||||
// Try to parse the UUID (from the username, if needed)
|
||||
final String rawUUID = invocation.getRawArguments()[3];
|
||||
@@ -112,7 +116,7 @@ public final class VerifiedCommand extends Subcommand {
|
||||
: UUID.nameUUIDFromBytes(("OfflinePlayer:" + rawUUID).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress, uuid)) {
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, uuid)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.already"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
@@ -121,7 +125,7 @@ public final class VerifiedCommand extends Subcommand {
|
||||
|
||||
// Add verified player to the database
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(rawAddress, uuid, timestamp));
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(inetAddress, uuid, timestamp));
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.add"),
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import java.net.InetSocketAddress;
|
||||
* Simple utility to determine if someone joins using GeyserMC
|
||||
*/
|
||||
@UtilityClass
|
||||
public class GeyserDetection {
|
||||
public class GeyserUtil {
|
||||
// https://github.com/GeyserMC/Floodgate/blob/master/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java#L206
|
||||
private final AttributeKey<Object> PLAYER_ATTRIBUTE = AttributeKey.valueOf("floodgate-player");
|
||||
|
||||
@@ -15,6 +15,27 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2021 Andrew Steinborn
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies
|
||||
* or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -22,6 +43,7 @@ import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import io.netty.util.Version;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -41,6 +63,25 @@ public class ProtocolUtil {
|
||||
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
|
||||
public static final String BRAND_CHANNEL = "minecraft:brand";
|
||||
|
||||
public static void checkNettyVersion() {
|
||||
final Version version = Version.identify().getOrDefault("netty-all", Version.identify().get("netty-common"));
|
||||
|
||||
// We're pretty much only doing this to avoid incompatibilities on Bukkit,
|
||||
// so we don't really care if the version couldn't be resolved.
|
||||
if (version == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] artifactVersion = version.artifactVersion().split("\\.");
|
||||
final int major = Integer.parseInt(artifactVersion[0]);
|
||||
final int minor = Integer.parseInt(artifactVersion[1]);
|
||||
|
||||
// Enforce Netty >4.1.x
|
||||
if (major < 4 || (major == 4 && minor < 1)) {
|
||||
throw new IllegalStateException("Your Netty version is too old to run Sonar! Please use Netty >4.1.x.");
|
||||
}
|
||||
}
|
||||
|
||||
public static int readVarInt(final ByteBuf byteBuf) {
|
||||
int read = readVarIntSafely(byteBuf);
|
||||
if (read == Integer.MIN_VALUE) {
|
||||
@@ -97,6 +138,19 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
|
||||
|
||||
static {
|
||||
for (int i = 0; i <= 32; ++i) {
|
||||
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
|
||||
}
|
||||
VARINT_EXACT_BYTE_LENGTHS[32] = 1;
|
||||
}
|
||||
|
||||
public static int varIntBytes(final int value) {
|
||||
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
|
||||
}
|
||||
|
||||
public static void writeVarLong(final ByteBuf byteBuf, final long value) {
|
||||
// Peel the one and two byte count cases explicitly as they are the most common VarLong sizes
|
||||
// that the proxy will write, to improve inlining.
|
||||
@@ -160,9 +214,8 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void readUUID(final @NotNull ByteBuf byteBuf) {
|
||||
byteBuf.readLong(); // least
|
||||
byteBuf.readLong(); // most
|
||||
public static @NotNull UUID readUUID(final @NotNull ByteBuf byteBuf) {
|
||||
return new UUID(byteBuf.readLong(), byteBuf.readLong());
|
||||
}
|
||||
|
||||
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf) {
|
||||
|
||||
@@ -81,26 +81,24 @@ verification:
|
||||
gravity:
|
||||
# Měl by Sonar zkoušet pro gravitaci hráče (Doporučováno)
|
||||
enabled: true
|
||||
# Měl by Sonar zkoušet jestli se hráč dotýka bloku? (Doporučováno)
|
||||
check-collisions: true
|
||||
# Měl by Sonar přimět hráče, aby vyřešil CAPTCHA, pokud neuspějí při kontrole gravitace?
|
||||
captcha-on-fail: false
|
||||
# Maximální počet ticků, který má hráč padat aby byl puštěn na verifikační platformu
|
||||
max-movement-ticks: 8
|
||||
# Gamemode hráče při verifikaci
|
||||
# Typy: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: všechny komponenty UI jsou zobrazeny
|
||||
# - CREATIVE: životy a hunger neni zobrazen
|
||||
# - ADVENTURE: všechny komponenty UI jsou zobrazeny
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Kontroluje, zda hráč naráží do bloků, které se vytvořily pod ním
|
||||
collision:
|
||||
# Měl by Sonar zkoušet jestli se hráč dotýka bloku? (Doporučováno)
|
||||
enabled: true
|
||||
# Jaký typ bloku by měl Sonar použít pro kolizní platformu?
|
||||
# Typy: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Toto byste měli upravovat, pouze pokud víte, co děláte
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Kontroluje, zda klient odesílá správné pakety při montáži vozidla
|
||||
vehicle:
|
||||
# Typy: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Noví hráči budou vždy namontováni na vozidle (Doporučeno)
|
||||
# - DURING_ATTACK: Noví hráči budou namontováni na vozidlo pouze během útoku
|
||||
# - NEVER: Noví hráči nebudou nikdy namontováni na vozidlo
|
||||
timing: ALWAYS
|
||||
# Měl by sonar kontrolovat, zda hráč správně vstoupil na loď?
|
||||
enabled: true
|
||||
|
||||
# Nechá hráče verifikovat pomocí kódu na mapě ve hře
|
||||
map-captcha:
|
||||
@@ -175,24 +173,35 @@ verification:
|
||||
# Maximální počet login packetů, který musí hřáč poslat aby byl vyhozen
|
||||
max-login-packets: 256
|
||||
|
||||
# Je-li povoleno, přehrávač bude přenesen zpět na původní server
|
||||
# po úspěšném absolvování ověření robota.
|
||||
# Je-li povoleno, hráč bude přenesen zpět na původní server
|
||||
# po úspěšném absolvování ověření bota.
|
||||
# Tuto funkci představil Mojang ve verzi Minecraft 1.20.5
|
||||
# Tutoriál: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Měl by Sonar přenést hráče na původní server (místo toho, aby je kopl)?
|
||||
# Měl by Sonar přenést hráče na původní server (místo toho, aby je vyhodil)?
|
||||
# Aby to fungovalo, musíte tuto funkci povolit v konfiguraci vašeho serveru
|
||||
# Kromě toho možná budete chtít snížit množství omezení rychlosti přihlášení
|
||||
# provádí Velocity nebo jiné proxy/pluginy, protože to může zabránit
|
||||
# hráč nebyl převeden správně.
|
||||
# hráči správné přenesení.
|
||||
enabled: false
|
||||
# Na který server by měl Sonar přenést přehrávač, když projde ověřením?
|
||||
# Na který server by měl Sonar přenést hráče, když projde ověřením?
|
||||
# Zadejte IP serveru, který hráči používají k běžnému připojení k vašemu serveru.
|
||||
# Například můžete zadat "mc.hypixel.net" nebo přímou IP jako "1.1.1.1"
|
||||
destination-host: "play.my-server.com"
|
||||
# Jaký port by měl Sonar použít při přenosu přehrávače na původní server?
|
||||
# Jaký port by měl Sonar použít při přenosu hráče na původní server?
|
||||
# Pokud váš server nepotřebuje port pro připojení, můžete toto ponechat jako 25565.
|
||||
destination-port: 25565
|
||||
|
||||
# Gamemode hráče při verifikaci
|
||||
# Typy: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: všechny komponenty UI jsou zobrazeny
|
||||
# - CREATIVE: životy a hunger neni zobrazen
|
||||
# - ADVENTURE: všechny komponenty UI jsou zobrazeny
|
||||
gamemode: ADVENTURE
|
||||
# Sonar ukládá do mezipaměti většinu paketů, které odesílá klientovi, včetně paketu LoginSuccess.
|
||||
# Které uživatelské jméno má Sonar použít pro paket LoginSuccess?
|
||||
# Uživatelské jméno nebude pro hráče viditelné bez úpravy klienta
|
||||
cached-username: "Sonar"
|
||||
# Měl by Sonar posílat nové verifikace?
|
||||
log-connections: true
|
||||
# Měl by Sonar posílat nové verifikace při útoku?
|
||||
@@ -222,10 +231,8 @@ verification:
|
||||
# Kolikrát musí hráč selhat verifikaci aby byl přídán do černé listiny
|
||||
# (Pokuď černou listinu nechcete dejte tuto hodnotu na 0)
|
||||
blacklist-threshold: 2
|
||||
# List ID protokolů (verzí), které nebudou kontrolovány Sonarem (bypass verifikace)
|
||||
# List ID protokolů které se nebudou moct připojit vůbec
|
||||
# Celý list ID protokolů můžete najít zde
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Například, Minecraft 1.20 má ID 763.
|
||||
whitelisted-protocols: []
|
||||
# List ID protokolů které se nebudou moct připojit vůbec
|
||||
blacklisted-protocols: []
|
||||
@@ -78,26 +78,24 @@ verification:
|
||||
gravity:
|
||||
# Soll Sonar auf gültige Client-Gravitation prüfen? (Empfohlen)
|
||||
enabled: true
|
||||
# Soll Sonar prüfen, ob der Spieler mit Blöcken kollidiert? (Empfohlen)
|
||||
check-collisions: true
|
||||
# Soll Sonar den Spieler dazu bringen, ein CAPTCHA zu lösen, wenn er die Schwerkraftprüfung nicht besteht?
|
||||
captcha-on-fail: false
|
||||
# Maximale Anzahl an Ticks, die der Spieler fallen muss, um die Plattform erreichen zu dürfen
|
||||
max-movement-ticks: 8
|
||||
# Der Spielmodus des Spielers während der Überprüfung
|
||||
# Mögliche Typen: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: alle UI-Komponenten sind sichtbar
|
||||
# - CREATIVE: Gesundheit und Hunger sind versteckt
|
||||
# - ADVENTURE: alle UI-Komponenten sind sichtbar
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Prüft, ob der Spieler mit Blöcken kollidiert, die unter ihm gespawnt sind
|
||||
collision:
|
||||
# Soll Sonar prüfen, ob der Spieler mit Blöcken kollidiert? (Empfohlen)
|
||||
enabled: true
|
||||
# Welche Art von Block sollte Sonar für die Kollisionsplattform verwenden?
|
||||
# Mögliche Typen: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Sie sollten dies nur bearbeiten, wenn Sie wissen, was Sie tun
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Überprüft, ob der Spieler beim Einsteigen in ein Boot die richtigen Pakete sendet
|
||||
vehicle:
|
||||
# Mögliche Typen: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Neue Spieler werden immer auf einem Fahrzeug montiert (Empfohlen)
|
||||
# - DURING_ATTACK: Neue Spieler werden nur während eines Angriffs auf ein Fahrzeug gesetzt
|
||||
# - NEVER: Neue Spieler werden niemals in ein Fahrzeug eingebaut
|
||||
timing: ALWAYS
|
||||
# Sollte Sonar prüfen, ob der Spieler richtig in ein Fahrzeug einsteigt?
|
||||
enabled: true
|
||||
|
||||
# Lässt den Spieler einen Code von einer virtuellen Karte im Chat eingeben
|
||||
map-captcha:
|
||||
@@ -172,12 +170,13 @@ verification:
|
||||
# Maximale Anzahl an Login-Paketen, die der Spieler senden muss, um gekickt zu werden
|
||||
max-login-packets: 256
|
||||
|
||||
# Wenn aktiviert, wird der Player zurück zum Ursprungsserver gesendet
|
||||
# Wenn aktiviert, wird der Player zurück zum Ursprungsserver gesendet,
|
||||
# falls dieser die der Bot-Verifizierung erfolgreich besteht.
|
||||
# Diese Funktion wurde von Mojang in Minecraft Version 1.20.5 eingeführt
|
||||
# Anleitung: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Sollte Sonar den Spieler auf den Ursprungsserver senden (anstatt ihn zu kicken)?
|
||||
# Damit dies funktioniert, müssen Sie die Funktion in der Konfiguration Ihres Servers aktivieren
|
||||
# Damit dies funktioniert, müssen Sie die Funktion in der Konfiguration Ihres Servers aktivieren.
|
||||
# Darüber hinaus möchten Sie möglicherweise die Begrenzung der Anmelderate reduzieren
|
||||
# wird von Velocity oder anderen Proxys/Plugins durchgeführt, da dies das verhindern könnte
|
||||
# Spieler konnte nicht korrekt übertragen werden.
|
||||
@@ -190,6 +189,16 @@ verification:
|
||||
# Wenn Ihr Server keinen Port zum Herstellen einer Verbindung benötigt, können Sie diesen auf 25565 belassen.
|
||||
destination-port: 25565
|
||||
|
||||
# Der Spielmodus des Spielers während der Überprüfung
|
||||
# Mögliche Typen: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: alle UI-Komponenten sind sichtbar
|
||||
# - CREATIVE: Gesundheit und Hunger sind versteckt
|
||||
# - ADVENTURE: alle UI-Komponenten sind sichtbar
|
||||
gamemode: ADVENTURE
|
||||
# Sonar speichert die meisten Pakete, die es an den Client sendet, einschließlich des LoginSuccess-Pakets.
|
||||
# Welchen Benutzernamen soll Sonar für das LoginSuccess-Paket verwenden?
|
||||
# Der Benutzername ist für den Spieler ohne Änderungen am Client nicht sichtbar
|
||||
cached-username: "Sonar"
|
||||
# Soll Sonar neue Überprüfungsversuche protokollieren?
|
||||
log-connections: true
|
||||
# Soll Sonar neue Überprüfungsversuche während Angriffen protokollieren?
|
||||
@@ -219,10 +228,8 @@ verification:
|
||||
# Wie oft muss ein Spieler die Überprüfung nicht bestehen, um auf die Schwarze Liste gesetzt zu werden?
|
||||
# (Setzen Sie diesen Wert auf 0, um die Schwarze Liste vollständig zu deaktivieren)
|
||||
blacklist-threshold: 2
|
||||
# Liste von Protokoll-IDs, die von Sonar nicht überprüft werden (Überprüfungsumgehung)
|
||||
# Liste von Protokoll-IDs, die den Server überhaupt nicht betreten dürfen
|
||||
# Die vollständige Liste aller Protokoll-IDs finden Sie hier:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Zum Beispiel hat Minecraft 1.20 die ID 763.
|
||||
whitelisted-protocols: []
|
||||
# Liste von Protokoll-IDs, die den Server überhaupt nicht betreten dürfen
|
||||
blacklisted-protocols: []
|
||||
@@ -80,26 +80,24 @@ verification:
|
||||
gravity:
|
||||
# Should Sonar check for valid client gravity? (Recommended)
|
||||
enabled: true
|
||||
# Should Sonar check if the player collides with blocks? (Recommended)
|
||||
check-collisions: true
|
||||
# Should Sonar make the player solve a CAPTCHA if they fail the gravity check?
|
||||
captcha-on-fail: false
|
||||
# Maximum number of ticks the player has to fall to be allowed to hit the platform
|
||||
max-movement-ticks: 8
|
||||
# The gamemode of the player during verification
|
||||
# Possible types: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: all UI components are visible
|
||||
# - CREATIVE: health and hunger are hidden
|
||||
# - ADVENTURE: all UI components are visible
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Checks if the client sends proper packets when mounting a vehicle
|
||||
# Checks if the player is colliding with blocks spawned below them
|
||||
collision:
|
||||
# Should Sonar check if the player collides with blocks? (Recommended)
|
||||
enabled: true
|
||||
# Which type of block should Sonar use for the collision platform?
|
||||
# Possible types: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# You should only edit this if you know what you're doing
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Checks if the client sends proper packets when entering a boat
|
||||
vehicle:
|
||||
# Possible types: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: New players will always be mounted on a vehicle (Recommended)
|
||||
# - DURING_ATTACK: New players will only be mounted on a vehicle during an attack
|
||||
# - NEVER: New players will never be mounted on a vehicle
|
||||
timing: ALWAYS
|
||||
# Should Sonar check if the player properly enters a boat?
|
||||
enabled: true
|
||||
|
||||
# Make the player type a code from a virtual map in chat
|
||||
map-captcha:
|
||||
@@ -177,21 +175,32 @@ verification:
|
||||
# If enabled, the player will be transferred back to the origin server
|
||||
# after successfully passing the bot verification.
|
||||
# This feature was introduced by Mojang in Minecraft version 1.20.5
|
||||
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Should Sonar transfer the player to the origin server (instead of kicking them)?
|
||||
# For this to work, you must enable the feature in your server's configuration
|
||||
# For this to work, you must enable the feature in your server's configuration.
|
||||
# Additionally, you might want to reduce the amount of login rate-limiting
|
||||
# performed by Velocity or other proxies/plugins, as this might prevent the
|
||||
# player from being transferred correctly.
|
||||
enabled: false
|
||||
# Which server should Sonar transfer the player to when the verification is passed?
|
||||
# Please enter the server IP that is used by players to normally connect to your server.
|
||||
# Please enter the server IP used by players to normally connect to your server.
|
||||
# For example, you can put "mc.hypixel.net" or a direct IP like "1.1.1.1"
|
||||
destination-host: "play.my-server.com"
|
||||
# Which port should Sonar use when transferring the player to the origin server?
|
||||
# If your server does not need a port to connect to, you can leave this as 25565.
|
||||
destination-port: 25565
|
||||
|
||||
# The gamemode of the player during verification
|
||||
# Possible types: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: all UI components are visible
|
||||
# - CREATIVE: health and hunger are hidden
|
||||
# - ADVENTURE: all UI components are visible
|
||||
gamemode: ADVENTURE
|
||||
# Sonar caches most packets it sends to the client, including the LoginSuccess packet.
|
||||
# Which username should Sonar use for the LoginSuccess packet?
|
||||
# The username will not be visible to the player without client modifications
|
||||
cached-username: "Sonar"
|
||||
# Should Sonar log new verification attempts?
|
||||
log-connections: true
|
||||
# Should Sonar log new verification attempts during attacks?
|
||||
@@ -221,10 +230,8 @@ verification:
|
||||
# How often does a player have to fail the verification in order to be blacklisted?
|
||||
# (Set this value to 0 to disable the blacklist entirely)
|
||||
blacklist-threshold: 2
|
||||
# List of protocol IDs which are not checked by Sonar (verification bypass)
|
||||
# List of protocol IDs which are unable to join the server at all
|
||||
# You can find the full list of all protocol IDs here:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# For example, Minecraft 1.20 has the ID 763.
|
||||
whitelisted-protocols: []
|
||||
# List of protocol IDs which are unable to join the server at all
|
||||
blacklisted-protocols: []
|
||||
@@ -80,26 +80,24 @@ verification:
|
||||
gravity:
|
||||
# Sonar devrait-il vérifier la gravité du client ? (Recommandé)
|
||||
enabled: true
|
||||
# Sonar devrait-il vérifier si le joueur entre en collision avec des blocs ? (Recommandé)
|
||||
check-collisions: true
|
||||
# Sonar devrait-il faire résoudre un CAPTCHA au joueur s'il échoue à la vérification de la gravité ?
|
||||
captcha-on-fail: false
|
||||
# Nombre maximum de ticks que le joueur a pour tomber avant de pouvoir toucher la plateforme
|
||||
max-movement-ticks: 8
|
||||
# Le mode de jeu du joueur pendant la vérification
|
||||
# Types possibles : SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL : tous les composants de l'interface utilisateur sont visibles
|
||||
# - CREATIVE : la santé et la faim sont cachées
|
||||
# - ADVENTURE : tous les composants de l'interface utilisateur sont visibles
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Vérifie si le joueur entre en collision avec les blocs apparus en dessous de lui
|
||||
collision:
|
||||
# Sonar devrait-il vérifier si le joueur entre en collision avec des blocs ? (Recommandé)
|
||||
enabled: true
|
||||
# Quel type de bloc Sonar doit-il utiliser pour la plate-forme de collision ?
|
||||
# Types possibles : STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Vous ne devriez modifier cela que si vous savez ce que vous faites
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Vérifie si le client envoie les paquets appropriés lorsqu'il monte dans un véhicule
|
||||
vehicle:
|
||||
# Types possibles : ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS : Les nouveaux joueurs seront toujours montés dans un véhicule (Recommandé)
|
||||
# - DURING_ATTACK : Les nouveaux joueurs ne seront montés dans un véhicule que pendant une attaque
|
||||
# - NEVER : Les nouveaux joueurs ne seront jamais montés dans un véhicule
|
||||
timing: ALWAYS
|
||||
# Sonar devrait-il vérifier si le joueur entre correctement dans un bateau ?
|
||||
enabled: true
|
||||
|
||||
# Fait taper au joueur un code à partir d'une carte virtuelle dans le chat
|
||||
map-captcha:
|
||||
@@ -177,6 +175,7 @@ verification:
|
||||
# S'il est activé, le joueur sera renvoyé au serveur d'origine
|
||||
# après avoir passé avec succès la vérification du bot.
|
||||
# Cette fonctionnalité a été introduite par Mojang dans la version Minecraft 1.20.5
|
||||
# Tutoriel: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Sonar devrait-il transférer le joueur au serveur d'origine (au lieu de l'expulser) ?
|
||||
# Pour que cela fonctionne, vous devez activer la fonctionnalité dans la configuration de votre serveur
|
||||
@@ -192,6 +191,16 @@ verification:
|
||||
# Si votre serveur n'a pas besoin d'un port pour se connecter, vous pouvez laisser celui-ci comme 25565.
|
||||
destination-port: 25565
|
||||
|
||||
# Le mode de jeu du joueur pendant la vérification
|
||||
# Types possibles : SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL : tous les composants de l'interface utilisateur sont visibles
|
||||
# - CREATIVE : la santé et la faim sont cachées
|
||||
# - ADVENTURE : tous les composants de l'interface utilisateur sont visibles
|
||||
gamemode: ADVENTURE
|
||||
# Sonar met en cache la plupart des paquets qu'il envoie au client, y compris le paquet LoginSuccess.
|
||||
# Quel nom d'utilisateur Sonar doit-il utiliser pour le paquet LoginSuccess ?
|
||||
# Le nom d'utilisateur ne sera pas visible par le joueur sans modification du client
|
||||
cached-username: "Sonar"
|
||||
# Sonar devrait-il journaliser les nouvelles tentatives de vérification ?
|
||||
log-connections: true
|
||||
# Sonar devrait-il journaliser les nouvelles tentatives de vérification pendant les attaques ?
|
||||
@@ -221,10 +230,8 @@ verification:
|
||||
# Combien de fois un joueur doit-il échouer à la vérification pour être placé sur liste noire ?
|
||||
# (Réglez cette valeur à 0 pour désactiver la liste noire entièrement)
|
||||
blacklist-threshold: 2
|
||||
# Liste des identifiants de protocole qui ne sont pas vérifiés par Sonar (bypass de vérification)
|
||||
# Liste des identifiants de protocole qui ne peuvent pas du tout rejoindre le serveur
|
||||
# Vous pouvez trouver la liste complète de tous les identifiants de protocole ici :
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Par exemple, Minecraft 1.20 a l'ID 763.
|
||||
whitelisted-protocols: []
|
||||
# Liste des identifiants de protocole qui ne peuvent pas du tout rejoindre le serveur
|
||||
blacklisted-protocols: []
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
# ზოგადი პარამეტრები Sonar-ისთვის
|
||||
general:
|
||||
# Should Sonar automatically check for new updates?
|
||||
# Sonar uses the GitHub API to check if the current version is outdated
|
||||
# შეამოწმოს თუ არა სონარმა ავტომატურად ახალი განახლებები?
|
||||
# სონარი იყენებს GitHub API_ს რომ შეამოწმოს არის თუ არა ამჟამინდელი ვერსია ყველაზე ახალი.
|
||||
check-for-updates: true
|
||||
# უნდა ჩაწეროს თუ არა Sonar-მა მოთამაშეების IP მისამართები კონსოლში?
|
||||
log-player-addresses: true
|
||||
# მაქსიმალური ერთი და იგივე IP ზე ონლაინ მოთამაშეთა რაოდენობა
|
||||
# ერთი და იგივე IP ზე ონლაინ მოთამაშეთა მაქსიმალური რაოდენობა
|
||||
max-online-per-ip: 3
|
||||
|
||||
# მნიშვნელოვანი პარამეტრები თავდასხმის დაფიქსირებისთვის
|
||||
@@ -31,7 +31,7 @@ attack-tracker:
|
||||
# დრო, რის შემდეგაც შეტევა დასრულებული ჩაითვლება
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
min-attack-duration: 30000
|
||||
# რამდენჯერ უნდა მოხდეს ზედა პარამეტრები, რათა ჩაითვალოს ეს თავდასხმად
|
||||
# რამდენჯერ უნდა მოხდეს ინციდენტის დარეპორტება, რათა ჩაითვალოს ეს თავდასხმად
|
||||
# ეს რიცხვი მოქმედებს ყალბი თავდასხმის შეტყობინებების გასაფილტრად
|
||||
min-attack-threshold: 2
|
||||
# დრო, რომელიც უნდა გავიდეს ახალი შეტევის დასადგენად
|
||||
@@ -41,10 +41,10 @@ attack-tracker:
|
||||
# თქვენ შეგიძლიათ დააკავშიროთ Sonar მონაცემთა ბაზას, რომ შეინახოთ ვერიფირებული მოთამაშეები თქვენი სერვერის გადატვირთვის შემდეგაც კი
|
||||
# შენიშვნა: IP მისამართები ინახება უბრალო ტექსტში. თქვენ ხართ პასუხისმგებელი თქვენი მონაცემთა ბაზის უსაფრთხოებაზე!
|
||||
database:
|
||||
# მონაცემთა ბაზის ტიპები, რომლებიც Sonar-ს შეუძლია გამოიყენოს ვერიფირებული მოთამაშეების შესანახად
|
||||
# მონაცემთა ბაზის ტიპები, რომლებიც Sonar-ს შეუძლია გამოიყენოს უკვე შემოწმებული მოთამაშეების შესანახად
|
||||
# შესაძლო ტიპები: NONE, MYSQL, MARIADB, H2
|
||||
type: NONE
|
||||
# File in Sonar's plugin directory used for the H2 database
|
||||
# ფაილი სონარ_ის ფოლდერში რომელიც ინახავს H2 database_ს
|
||||
filename: verified_players
|
||||
# ჰოსტის მისამართი SQL მონაცემთა ბაზის ავთენტიფიკაციისთვის
|
||||
host: localhost
|
||||
@@ -56,60 +56,58 @@ database:
|
||||
username: ''
|
||||
# პაროლი SQL მონაცემთა ბაზის ავთენტიფიკაციისთვის
|
||||
password: ''
|
||||
# რამდენი დღე უნდა შეინახოს Sonar-მა დამოწმებული მოთამაშეები მონაცემთა ბაზაში?
|
||||
# რამდენი დღე უნდა შეინახოს Sonar-მა შემოწმებული მოთამაშეები მონაცემთა ბაზაში?
|
||||
maximum-age: 5
|
||||
|
||||
# ყოველი ახალი შესვლის მოთხოვნა იქნება რიგში, რათა თავიდან აიცილოთ სპამის მიერთების შეტევები
|
||||
# ყოველი ახალი შესვლის მოთხოვნა იქნება რიგში, რათა თავიდან აიცილოთ შემოსვლის სპამის შეტევები
|
||||
# რიგი განახლდება ყოველ 1000 მილიწამში (20 ტიკი)
|
||||
queue:
|
||||
# ახალი მოთამაშეების მაქსიმალური რაოდენობა, რომლებსაც შეუძლიათ შესვლა სერვერზე ყოველ 1000 მილიწამში
|
||||
max-polls: 30
|
||||
|
||||
# ყოველი ახალი მოთამაშე, რომელიც პირველად შეუერთდება, გაიგზავნება
|
||||
# ყოველი ახალი მოთამაშე, რომელიც პირველად შემოვა, გაიგზავნება
|
||||
# ლიმბო სერვერში, სადაც მოთამაშეების ვერიფიკაცია ხდება
|
||||
verification:
|
||||
# როდის უნდა გადაამოწმოს Sonar-მა ახალი მოთამაშეები?
|
||||
# შესაძლო ტიპები: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: ახალი მოთამაშეები ყოველთვის მოწმდება (რეკომენდებულია)
|
||||
# - DURING_ATTACK: ახალი მოთამაშეები შემოწმდებიან მხოლოდ შეტევის დროსდროს
|
||||
# - NEVER: ახალი მოთამაშეები არასოდეს შემოწმდებიან
|
||||
# - ALWAYS (ყოველთვის): ახალი მოთამაშეები ყოველთვის მოწმდება (რეკომენდებულია)
|
||||
# - DURING_ATTACK (შეტევის დროს): ახალი მოთამაშეები შემოწმდებიან მხოლოდ შეტევის დროს
|
||||
# - NEVER (არასდროს): ახალი მოთამაშეები არასდროს შემოწმდებიან
|
||||
timing: ALWAYS
|
||||
checks:
|
||||
# ამოწმებს, მიჰყვება თუ არა მოთამაშეების დაცემის მოძრაობა Minecraft-ის გრავიტაციის ფორმულას
|
||||
# ყველა პროგნოზირებული მოძრაობა წინასწარ არის გათვლილი პერფორმანსის შესანარჩუნებლად
|
||||
# ამოწმებს, მიჰყვება თუ არა მოთამაშეების ვარდნის მოძრაობა Minecraft-ის გრავიტაციის ფორმულას
|
||||
# ყველა პროგნოზირებული მოძრაობა წინასწარ არის გათვლილი შესრულების სისწრაფის შესანარჩუნებლად
|
||||
gravity:
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა კლიენტის გრავიტაცია? (რეკომენდირებულია)
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა მოთამაშის (ან ბოტის) გრავიტაცია? (რეკომენდებულია)
|
||||
enabled: true
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა, ეტაკება თუ არა მოთამაშე ბლოკებს?
|
||||
check-collisions: true
|
||||
# უნდა აიძულოს თუ არა Sonar-მა მოთამაშეს გადაჭრას CAPTCHA, თუ ისინი ვერ შეასრულებენ სიმძიმის შემოწმებას?
|
||||
# უნდა აიძულოს თუ არა Sonar-მა მოთამაშეს შეასრულოს CAPTCHA, თუ ისინი ვერ შეასრულებენ სიმძიმის შემოწმებას?
|
||||
captcha-on-fail: false
|
||||
# ტიკების მაქსიმალური რაოდენობა რომელითაც მოთამაშე უნდა დაეცეს, რათა პლატფორმაზე დაცემა შეეძლოს
|
||||
# ტიკების მაქსიმალური რაოდენობა რომელითაც მოთამაშე უნდა დაეცეს, რათა პლატფორმაზე დაცემა შეეძლოს. (20 ტიკი = 1 წამი)
|
||||
max-movement-ticks: 8
|
||||
# მოთამაშის თამაშის რეჟიმი გადამოწმების დროს
|
||||
# შესაძლო ტიპები: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: ყველა UI კომპონენტი ჩანს
|
||||
# - CREATIVE: სიცოცხლის და საჭმლის მაჩვენებლები იშლება
|
||||
# - ADVENTURE: ყველა UI კომპონენტი ჩანს
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Checks if the player is colliding with blocks spawned below them
|
||||
collision:
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა, ეტაკება თუ არა მოთამაშე ბლოკებს? (რეკომენდებულია)
|
||||
enabled: true
|
||||
# Which type of block should Sonar use for the collision platform?
|
||||
# შესაძლო ტიპები: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# თქვენ უნდა შეცვალოთ ეს მხოლოდ იმ შემთხვევაში, თუ იცით, რას აკეთებთ
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# ამოწმებს, აგზავნის თუ არა კლიენტი შესაბამის პაკეტებს სატრანსპორტო საშუალებაზე დაჯდომის დროს
|
||||
vehicle:
|
||||
# შესაძლო ტიპები: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: ახალი მოთამაშეები ყოველთვის დაჯდებიან სატრანსპორტო საშუალებაზე
|
||||
# - DURING_ATTACK: ახალი მოთამაშეები მხოლოდ ბოტების შემოტევის დროს დაჯდებიან სატრანსპორტო საშუალებაზე
|
||||
# - NEVER: ახალი მოთამაშეები არასოდეს დაჯდებიან სატრანსპორტო საშუალებაზე (რეკომენდებულია)
|
||||
timing: ALWAYS
|
||||
# Should Sonar check if the player properly enters a boat?
|
||||
enabled: true
|
||||
|
||||
# დააწერინეთ მოთამაშეს კოდი ჩატში, რომელიც იქნება ნაჩვენები ვირტუალურ მაპზე, რომელიც ხელში ეჭირებად
|
||||
# დააწერინეთ მოთამაშეს კოდი ჩატში, რომელიც იქნება ნაჩვენები ვირტუალურ მაპზე, რომელიც ხელში ეჭირებათ
|
||||
map-captcha:
|
||||
# როდის უნდა გააკეთებინოს Sonar-მა მოთამაშეს CAPTCHA?
|
||||
# შესაძლო ტიპები: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: ახალი მოთამაშეები ყოველთვის მოუწევთ CAPTCHA-ს გაკეთება
|
||||
# - DURING_ATTACK: ახალი მოთამაშებს მოუწევთ CAPTCHA-ს გაკეთება მხოლოდ შეტევის დროს
|
||||
# - NEVER: ახალ მოთამაშეებს არასდროს მოუწევთ CAPTCHA-ს გაკეთება (Recommended)
|
||||
# - ALWAYS (ყოველთვის): ახალი მოთამაშეები ყოველთვის მოუწევთ CAPTCHA-ს გაკეთება.
|
||||
# - DURING_ATTACK (შეტევის დროს): ახალი მოთამაშებს მოუწევთ CAPTCHA-ს გაკეთება მხოლოდ შეტევის დროს.
|
||||
# - NEVER (არასდროს): ახალ მოთამაშეებს არასდროს მოუწევთ CAPTCHA-ს გაკეთება (Recommended).
|
||||
timing: NEVER
|
||||
# ეფექტების პარამეტრები გამოსახულებაზე, რომელიც აჩვენა მოთამაშეს
|
||||
# ეფექტების პარამეტრები CAPTCHA გამოსახულებაზე, რომელსაც ვანახებთ მოთამაშეს
|
||||
# თქვენ უნდა შეცვალოთ ეს მხოლოდ იმ შემთხვევაში, თუ იცით, რას აკეთებთ
|
||||
effects:
|
||||
# მატებს შემთხვევითი ხაზების ეფექტს გამოსახულებაზე
|
||||
@@ -129,9 +127,9 @@ verification:
|
||||
# - 2: წრეები
|
||||
# - 3: კვადრატები
|
||||
shape: 2
|
||||
# მანძილი დამახინჯებულ პიქსელებს/ობიექტებს შორის
|
||||
# მანძილი არეულ პიქსელებს/ობიექტებს შორის
|
||||
distance: 5
|
||||
# დამახინჯებული პიქსელების/ობიექტების სიხშირე
|
||||
# არეული პიქსელების/ობიექტების სიხშირე
|
||||
density: 0.1
|
||||
# შერევის ხარისხი ობიექტების ფერებს შორის
|
||||
mix: 0.15
|
||||
@@ -144,7 +142,7 @@ verification:
|
||||
# თუ ეს გამორთულია, Sonar-ი გამოიყენებს შემთხვევით გრადიენტს ფერებისთვის
|
||||
auto-color: false
|
||||
# რამდენი პასუხი უნდა მოამზადოს Sonar-მა?
|
||||
# ეს ხდება ასინქრონულად უკანა ფონზე
|
||||
# ეს ხდება ასინქრონულად (ანუ ამის გამოთვლას სერვერი არ დაელოდება) უკანა ფონზე
|
||||
precompute: 500
|
||||
# რამდენ ხანს უნდა დაელოდოს Sonar-ი, სანამ მოთამაშე ვერ გააკეთებს წარმატებულად CAPTCHA-ს?
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
@@ -155,32 +153,33 @@ verification:
|
||||
# არ არის რეკომენდებული რიცხვების ან ასოების დამატება, რომლებიც შეიძლება ერთმანეთს ჰგავდეს
|
||||
dictionary: '1235689'
|
||||
|
||||
# ამოწმებს, აგზავნიან თუ არა მოთამაშეები სერვერზე რეალურ კლიენტის სახელს
|
||||
# ამოწმებს, აგზავნიან თუ არა მოთამაშეები სერვერზე რეალურ მოთამაშის მეტსახელს
|
||||
client-brand:
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა ნამდვილია თუ არა მოთამაშის კლიენტის სახელი? (რეკომენდებულია)
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა ნამდვილია თუ არა მოთამაშის სახელი? (რეკომენდებულია)
|
||||
enabled: true
|
||||
# Regex-ი კლიენტის ბრენდების დასადასტურებლად ვერიფიკაციის დროს
|
||||
# Regex-ი მოთამაშის ბრენდების დასადასტურებლად ვერიფიკაციის დროს
|
||||
valid-regex: ^[!-~ ]+$
|
||||
# მოთამაშის კლიენტის სახელის მაქსიმალური სიგრძე ვერიფიკაციის დროს
|
||||
max-length: 64
|
||||
|
||||
# Regex-ი მომხმარებლის სახელების ვერიფიკაციისთვის
|
||||
valid-name-regex: ^[a-zA-Z0-9_]+$
|
||||
# Regex-ი კლიენტის ლოკალის დასადასტურებლად ვერიფიკაციის დროს
|
||||
# Regex-ი კლიენტის ადგილის დასადასტურებლად ვერიფიკაციის დროს
|
||||
valid-locale-regex: ^[a-zA-Z_]+$
|
||||
# პინგი რამდენიც უნდა ქონდეს მოთამაშეს იმისთვის, რომ ტაიმაუტდეს
|
||||
# პინგი რამდენიც უნდა ქონდეს მოთამაშეს იმისთვის, რომ დროებით დაიბლოკოს (გავარდეს)
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
max-ping: 10000
|
||||
# მაქსიმალური რაოდენობა ლოგინ პაკეტების, რომელიც მოთამაშემ უნდა გააგზავნოს სანამ გაიკიკება სერვერიდან
|
||||
# მაქსიმალური რაოდენობა შესვლის (LOGIN) პაკეტების, რომელიც მოთამაშემ უნდა გააგზავნოს სანამ გავარდება სერვერიდან
|
||||
max-login-packets: 256
|
||||
|
||||
# თუ ჩართულია, მოთამაშე გადავა საწყისი სერვერში ერიფიკაციის წარმატებით გავლის შემდეგ
|
||||
# თუ ჩართულია, მოთამაშე გადავა საწყის სერვერში ვერიფიკაციის წარმატებით გავლის შემდეგ
|
||||
# ეს ფუნქცია დაემატა Mojang-ის მიერ Minecraft-ის 1.20.5 ვერსიაში
|
||||
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# უნდა გადაიყვანოს თუ არა Sonar-მა მოთამაშე საწყის სერვერზე (მათი გაკიკვის ნაცვლად)?
|
||||
# უნდა გადაიყვანოს თუ არა Sonar-მა მოთამაშე საწყის სერვერზე (მათი გაგდების ნაცვლად)?
|
||||
# ამისთვის, თქვენ უნდა ჩართოთ ეს ფუნქცია თქვენი სერვერის კონფიგურაციაში
|
||||
# დამატებით, შეიძლება გინდოდეთ შეამციროთ შესვლის rate-limiting ი
|
||||
# რომელსაც ახორციელებს Velocity ან სხვა პროქსები/პლაგინები, რადგან ეს შეიძლება ხელს უშლიდეს
|
||||
# დამატებით, შეიძლება გინდოდეთ შეამციროთ შესვლის სიხშირის ლიმიტი
|
||||
# რომელსაც ახორციელებს Velocity ან სხვა პროქსიები/პლაგინები, რადგან ეს შეიძლება ხელს უშლიდეს
|
||||
# მოთამაშის სწორად გადაყვანას
|
||||
enabled: false
|
||||
# რომელ სერვერზე უნდა გადაიყვანოს Sonar-მა მოთამაშე ვერიფიკაციის გავლის შემდეგ?
|
||||
@@ -191,22 +190,32 @@ verification:
|
||||
# თუ თქვენს სერვერს არ სჭირდება პორტი შესასვლელად, შეგიძლიათ დატოვოთ ეს 25565-ზე
|
||||
destination-port: 25565
|
||||
|
||||
# მოთამაშის თამაშის რეჟიმი გადამოწმების დროს
|
||||
# შესაძლო ტიპები: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: ყველა UI კომპონენტი ჩანს
|
||||
# - CREATIVE: სიცოცხლის და საჭმლის მაჩვენებლები იშლება
|
||||
# - ADVENTURE: ყველა UI კომპონენტი ჩანს
|
||||
gamemode: ADVENTURE
|
||||
# Sonar caches most packets it sends to the client, including the LoginSuccess packet.
|
||||
# Which username should Sonar use for the LoginSuccess packet?
|
||||
# The username will not be visible to the player without client modifications
|
||||
cached-username: "Sonar"
|
||||
# ჩაიწეროს თუ არა Sonar-მა ახალი ვერიფიკაციის მცდელობები?
|
||||
log-connections: true
|
||||
# ჩაწეროს თუ არა Sonar-მა ახალი ვერიფიკაციის მცდელობები შეტევის დროს?
|
||||
log-during-attack: false
|
||||
# ჩაიწეროს თუ არა Sonar-მა ყოველი მოძრაობა/პოზიცია ვერიფიკაციის დროს?
|
||||
# ეს არ არის რეკომენდირებული აქტიურ სერვერებისთვის, მაგრამ შეიძლება გამოსადეგი იყოს ცრუ პოსიტივების გამოსასწორებლად.
|
||||
# ეს არ არის რეკომენდებული აქტიურ სერვერებისთვის, მაგრამ შეიძლება გამოსადეგი იყოს ცრუ პოსიტივების გამოსასწორებლად.
|
||||
debug-xyz-positions: false
|
||||
# უნდა შეამოწმოს თუ არა Sonar-მა Geyser (Bedrock) მოთამაშეები?
|
||||
# ეს ფუნქცია ექსპერიმენტულია და შეიძლება გამოიწვიოს პრობლემები
|
||||
# თუ ეს გამორთულია, Bedrock მოთამაშეები გამოვლენილი იქნება
|
||||
check-geyser-players: true
|
||||
# დროის ოდენობა, რომელიც უნდა გავიდეს, სანამ მოთამაშე ტაიმაუტდება სერვერიდან.
|
||||
# დროის ოდენობა, რომელიც უნდა გავიდეს, სერვერთან კავშირის გარეშე, სანამ მოთამაშე დროებით გავარდება სერვერიდან.
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
read-timeout: 8000
|
||||
# The time since the server stopped sending packets before disconnecting the player
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
# დრო როდიდანაც სერვერმა შეწყვიტა პაკეტების გაგზავნა სანამ გააგდებს მოთამაშეს.
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
write-timeout: 10000
|
||||
# რამდენ ხანს უნდა დაელოდოს მოთამაშე ვერიფიკაციის დროს ხელახლა დაკავშირებამდე?
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
@@ -220,10 +229,8 @@ verification:
|
||||
# რამდენად ხშირად უნდა გაიაროს მოთამაშემ ვერიფიკაცია წარუმატებლად, რომ ის მოხვდეს შავ სიაში?
|
||||
# (დააყენეთ ეს 0 ზე, რომ მთლიანად გათიშოთ შავი სია)
|
||||
blacklist-threshold: 2
|
||||
# პროტოკოლის ID-ების სია, რომლებიც არ არის შემოწმებული Sonar-ის მიერ (დამოწმების შემოვლითი გზა)
|
||||
# პროტოკოლის ID-ების სია, რომლებსაც საერთოდ არ შეუძლიათ სერვერზე შემოსვლა
|
||||
# თქვენ შეგიძლიათ იპოვოთ ყველა პროტოკოლის ID-ების სრული სია აქ:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# მაგალითად, Minecraft 1.20-ს აქვს ID 763.
|
||||
whitelisted-protocols: []
|
||||
# პროტოკოლის ID-ების სია, რომლებსაც საერთოდ არ შეუძლიათ სერვერზე შემოსვლა
|
||||
blacklisted-protocols: []
|
||||
@@ -80,26 +80,24 @@ verification:
|
||||
gravity:
|
||||
# Moet Sonar controleren op geldige zwaartekracht van de cliënt? (Aanbevolen)
|
||||
enabled: true
|
||||
# Moet Sonar controleren of de speler tegen blokken botst? (Aanbevolen)
|
||||
check-collisions: true
|
||||
# Moet Sonar de speler een CAPTCHA laten oplossen als hij niet slaagt voor de zwaartekracht controle?
|
||||
captcha-on-fail: false
|
||||
# Maximaal aantal ticks dat de speler moet laten vallen om het platform te mogen raken
|
||||
max-movement-ticks: 8
|
||||
# De spelmodus van de speler tijdens verificatie
|
||||
# Mogelijke typen: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: alle UI-componenten zijn zichtbaar
|
||||
# - CREATIVE: gezondheid en honger zijn verborgen
|
||||
# - ADVENTURE: alle UI-componenten zijn zichtbaar
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Controleert of de speler botst met blokken die onder hem zijn gespawned
|
||||
collision:
|
||||
# Moet Sonar controleren of de speler tegen blokken botst? (Aanbevolen)
|
||||
enabled: true
|
||||
# Welk type blok moet Sonar gebruiken voor het platform?
|
||||
# Mogelijke typen: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Je moet dit alleen bewerken als je weet wat je doet
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Controleert of de klant de juiste pakketten verzendt bij het monteren van een voertuig
|
||||
vehicle:
|
||||
# Mogelijke typen: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Nieuwe spelers worden altijd op een voertuig gemonteerd (Aanbevolen)
|
||||
# - DURING_ATTACK: Nieuwe spelers worden alleen tijdens een aanval op een voertuig gemonteerd
|
||||
# - NEVER: Nieuwe spelers zullen nooit op een voertuig worden gemonteerd
|
||||
timing: ALWAYS
|
||||
# Moet Sonar controleren of de speler goed in een boot stapt?
|
||||
enabled: true
|
||||
|
||||
# Laat de speler een code van een virtuele kaart in de chat typen
|
||||
map-captcha:
|
||||
@@ -177,6 +175,7 @@ verification:
|
||||
# Indien ingeschakeld, wordt de speler teruggestuurd naar de oorspronkelijke server
|
||||
# nadat u de bot-verificatie met succes heeft doorstaan.
|
||||
# Deze functie is geïntroduceerd door Mojang in Minecraft versie 1.20.5
|
||||
# Handleiding: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Moet Sonar de speler overbrengen naar de oorspronkelijke server (in plaats van hem te schoppen)?
|
||||
# Om dit te laten werken, moet u de functie inschakelen in de configuratie van uw server
|
||||
@@ -192,6 +191,16 @@ verification:
|
||||
# Als uw server geen poort nodig heeft om verbinding mee te maken, kunt u dit op 25565 laten staan.
|
||||
destination-port: 25565
|
||||
|
||||
# De spelmodus van de speler tijdens verificatie
|
||||
# Mogelijke typen: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: alle UI-componenten zijn zichtbaar
|
||||
# - CREATIVE: gezondheid en honger zijn verborgen
|
||||
# - ADVENTURE: alle UI-componenten zijn zichtbaar
|
||||
gamemode: ADVENTURE
|
||||
# Sonar cached de meeste pakketten die het naar de client stuurt, inclusief het LoginSuccess pakket.
|
||||
# Welke gebruikersnaam moet Sonar gebruiken voor het LoginSucces pakket?
|
||||
# De gebruikersnaam zal niet zichtbaar zijn voor de speler zonder client aanpassingen
|
||||
cached-username: "Sonar"
|
||||
# Moet Sonar nieuwe verificatiepogingen registreren?
|
||||
log-connections: true
|
||||
# Moet Sonar nieuwe verificatiepogingen registreren tijdens aanvallen?
|
||||
@@ -221,10 +230,8 @@ verification:
|
||||
# Hoe vaak moet een speler de verificatie niet doorstaan om op de zwarte lijst te worden geplaatst?
|
||||
# (Stel deze waarde in op 0 om de zwarte lijst volledig uit te schakelen)
|
||||
blacklist-threshold: 2
|
||||
# Lijst met protocol-ID's die niet door Sonar worden gecontroleerd (verificatie-bypass)
|
||||
# Lijst met protocol-ID's die helemaal geen verbinding kunnen maken met de server
|
||||
# De volledige lijst met alle protocol-ID's vindt u hier:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Minecraft 1.20 heeft bijvoorbeeld de ID 763.
|
||||
whitelisted-protocols: []
|
||||
# Lijst met protocol-ID's die helemaal geen verbinding kunnen maken met de server
|
||||
blacklisted-protocols: []
|
||||
@@ -0,0 +1,237 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# Potrzebujesz pomocy lub masz pytania? https://jonesdev.xyz/discord
|
||||
# Source code: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Ustawienia ogólne Sonar
|
||||
general:
|
||||
# Czy Sonar powinien automatycznie sprawdzać dostępność nowych wersji?
|
||||
# Sonar korzysta z API GitHub, aby sprawdzić, czy bieżąca wersja jest nieaktualna
|
||||
check-for-updates: true
|
||||
# Czy Sonar powinien rejestrować adresy IP graczy w konsoli?
|
||||
log-player-addresses: true
|
||||
# Maksymalna liczba graczy online z tym samym adresem IP
|
||||
max-online-per-ip: 3
|
||||
|
||||
# Ustawienia czułości dla funkcji śledzenia ataku
|
||||
# Edytuj to tylko wtedy, gdy wiesz, co robisz
|
||||
attack-tracker:
|
||||
# Minimalna liczba nowych graczy do wykrycia ataku
|
||||
min-players-for-attack: 8
|
||||
# Czas, który musi upłynąć, aby tryb ataku się zakończył.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
min-attack-duration: 30000
|
||||
# Ile razy incydent musi zostać zgłoszony, aby został uznany za atak?
|
||||
# Liczba ta działa jak bufor do filtrowania fałszywych powiadomień o atakach
|
||||
min-attack-threshold: 2
|
||||
# Czas, który musi upłynąć, aby nowy atak mógł zostać wykryty.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
attack-cooldown-delay: 3000
|
||||
|
||||
# Sonar można podłączyć do bazy danych, aby zachować zweryfikowanych graczy nawet po ponownym uruchomieniu serwera.
|
||||
# Uwaga: Adresy IP są zapisywane w postaci zwykłego tekstu. Jesteś odpowiedzialny za bezpieczeństwo swojej bazy danych!
|
||||
database:
|
||||
# Typ bazy danych używanej przez Sonar.
|
||||
# Dostępne rodzaje: NONE, MYSQL, MARIADB, H2
|
||||
type: NONE
|
||||
# Plik w folderze pluginu używany dla bazy danych H2
|
||||
filename: verified_players
|
||||
# Adres hosta do uwierzytelniania w bazie danych SQL
|
||||
host: localhost
|
||||
# Port do uwierzytelniania z bazą danych SQL
|
||||
port: 3306
|
||||
# Nazwa bazy danych SQL
|
||||
name: sonar
|
||||
# Nazwa użytkownika do uwierzytelniania w bazie danych SQL
|
||||
username: ''
|
||||
# Hasło do uwierzytelniania w bazie danych SQL
|
||||
password: ''
|
||||
# Przez ile dni Sonar powinien przechowywać zweryfikowanych graczy w bazie danych?
|
||||
maximum-age: 5
|
||||
|
||||
# Każde nowe logowanie będzie kolejkowane w celu uniknięcia ataków typu spam join.
|
||||
# Kolejka jest aktualizowana co 1 sekundę (20 tików).
|
||||
queue:
|
||||
# Maksymalna liczba jednoczesnych odpytywań kolejki na 1 sekundę
|
||||
max-polls: 30
|
||||
|
||||
# Każdy nowy gracz, który dołączy po raz pierwszy, zostanie wysłany do
|
||||
# lekkiego serwera Limbo, na którym przeprowadzane są zaawansowane kontrole antybotowe
|
||||
verification:
|
||||
# Kiedy Sonar powinien weryfikować nowych graczy?
|
||||
# Możliwe rodzaje: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Nowi gracze będą zawsze weryfikowani (zalecane)
|
||||
# - DURING_ATTACK: Nowi gracze będą weryfikowani tylko podczas ataku
|
||||
# - NEVER: Nowi gracze nigdy nie będą weryfikowani
|
||||
timing: ALWAYS
|
||||
checks:
|
||||
# Sprawdza, czy ruch spadający gracza jest zgodny z działaniem grawitacji Minecraft.
|
||||
# Wszystkie przewidywane ruchy są wstępnie obliczane w celu zachowania wydajności
|
||||
gravity:
|
||||
# Czy Sonar powinien sprawdzać poprawność grawitacji gracza? (Zalecane)
|
||||
enabled: true
|
||||
# Czy Sonar powinien zmusić gracza do rozwiązania CAPTCHA, jeśli nie uda mu się przejść testu grawitacji?
|
||||
captcha-on-fail: false
|
||||
# Maksymalna liczba tików, po których gracz musi spaść, aby móc uderzyć w platformę.
|
||||
max-movement-ticks: 8
|
||||
|
||||
# Sprawdza, czy gracz zderza się z blokami, które pojawiły się pod nim
|
||||
collision:
|
||||
# Czy Sonar powinien sprawdzać, czy gracz zderza się z blokami? (Zalecane)
|
||||
enabled: true
|
||||
# Jakiego typu bloku powinien użyć Sonar dla platformy kolizyjnej?
|
||||
# Możliwe rodzaje: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Powinieneś to edytować tylko wtedy, gdy wiesz, co robisz
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Sprawdza, czy gracz wysyła odpowiednie pakiety podczas umieszczania na pojeździe.
|
||||
vehicle:
|
||||
# Czy Sonar powinien sprawdzać, czy gracz prawidłowo wsiada do łodzi?
|
||||
enabled: true
|
||||
|
||||
# Zmuszenie gracza do wpisania kodu z wirtualnej mapy na czacie (CAPTCHA).
|
||||
map-captcha:
|
||||
# Kiedy Sonar powinien zmusić gracza do rozwiązania CAPTCHA?
|
||||
# Dostępne rodzaje: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Nowi gracze zawsze otrzymają weryfikacje CAPTCHA
|
||||
# - DURING_ATTACK: Nowi gracze otrzymają weryfikacje CAPTCHA tylko podczas ataku.
|
||||
# - NEVER: Nowi gracze nigdy nie otrzymają weryfikacji CAPTCHA (zalecane)
|
||||
timing: NEVER
|
||||
# Opcje efektów na mapie wyświetlanej graczowi
|
||||
# Powinieneś to edytować tylko wtedy, gdy wiesz, co robisz
|
||||
effects:
|
||||
# Dodawanie losowych linii do obrazu
|
||||
scratches: true
|
||||
# Stosuje efekt falowania
|
||||
ripple: true
|
||||
# Stosuje filtr nierówności (wyostrzony tekst)
|
||||
bump: true
|
||||
# Filtr zniekształceń
|
||||
distortion:
|
||||
# Czy Sonar powinien stosować filtr zniekształceń?
|
||||
enabled: true
|
||||
# Jakiego kształtu filtra zniekształceń powinien używać Sonar?
|
||||
# Dostępne rodzaje: 0, 1, 2, 3
|
||||
# - 0: Krzyże
|
||||
# - 1: Linie
|
||||
# - 2: Kółka
|
||||
# - 3: Kwadraty
|
||||
shape: 2
|
||||
# Odległość między zniekształconymi pikselami/obiektami
|
||||
distance: 5
|
||||
# Gęstość zniekształconych pikseli/obiektów
|
||||
density: 0.1
|
||||
# Stopień mieszania się kolorów obiektów
|
||||
mix: 0.15
|
||||
# Ścieżka do obrazu tła używanego przez Sonar dla CAPTCHA
|
||||
# Jeśli obraz nie zostanie znaleziony, jako domyślne tło zostanie użyty szum
|
||||
# Możesz umieścić nazwę pliku tutaj, jeśli plik znajduje się w katalogu pluginu
|
||||
background: ''
|
||||
# Czy Sonar powinien automatycznie wykrywać kolor tła?
|
||||
# Ma to na celu stworzenie kontrastu między obrazem tła a tekstem
|
||||
# Jeśli ta opcja jest wyłączona, Sonar użyje losowego gradientu dla kolorów
|
||||
auto-color: false
|
||||
# Ile możliwych odpowiedzi powinien przygotować Sonar?
|
||||
# Zadanie to wykonywane jest asynchronicznie w tle
|
||||
precompute: 500
|
||||
# Jak długo Sonar powinien czekać, aż gracz nie przepisze kodu CAPTCHA, zanim go wyrzuci?
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
max-duration: 30000
|
||||
# Ile razy gracz musi przepisać błędnie kod CAPTCHA przed wyrzuceniem z serwera?
|
||||
max-tries: 3
|
||||
# Znaki (litery i cyfry), które mogą pojawić się w CAPTCHA
|
||||
# Nie zaleca się dodawania cyfr lub liter, które mogą być do siebie podobne
|
||||
dictionary: '1235689'
|
||||
|
||||
# Sprawdza, czy gracz wysyła prawidłowy client brand do serwera.
|
||||
client-brand:
|
||||
# Czy Sonar powinien sprawdzać poprawność client brand? (Zalecane)
|
||||
enabled: true
|
||||
# Regex do sprawdzania poprawności client brand podczas weryfikacji
|
||||
valid-regex: ^[!-~ ]+$
|
||||
# Maksymalna długość wartości client brand podczas weryfikacji
|
||||
max-length: 64
|
||||
|
||||
# Regex do sprawdzania poprawności nazw użytkowników podczas weryfikacji
|
||||
valid-name-regex: ^[a-zA-Z0-9_]+$
|
||||
# Regex do sprawdzania ustawień regionalnych klienta podczas weryfikacji
|
||||
valid-locale-regex: ^[a-zA-Z_]+$
|
||||
# Maksymalny ping gracza, zanim zostanie wyrzucony z serwera.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
max-ping: 10000
|
||||
# Liczba pakietów logowania, które gracz musi wysłać, aby zostać wyrzuconym.
|
||||
max-login-packets: 256
|
||||
|
||||
# Jeśli opcja ta jest włączona, gracz zostanie przeniesiony z powrotem
|
||||
# na serwer początkowy po pomyślnym przejściu weryfikacji antybotowej.
|
||||
# Funkcja ta została wprowadzona przez Mojang w wersji 1.20.5
|
||||
# Tutorial: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Czy Sonar powinien przenieść gracza na serwer początkowy (zamiast go wyrzucać)?
|
||||
# Aby to zadziałało, musisz włączyć tę funkcję w konfiguracji serwera.
|
||||
# Dodatkowo, możesz chcieć zmniejszyć limitowanie prędkości logowania (ratelimit).
|
||||
# wykonywane przez Velocity lub inne silniki/pluginy, ponieważ
|
||||
# może to uniemożliwić prawidłowe przeniesienie gracza.
|
||||
enabled: false
|
||||
# Na który serwer Sonar powinien przenieść gracza po pomyślnym przejściu weryfikacji?
|
||||
# Wprowadź adres IP serwera, który jest używany przez graczy do normalnego łączenia się z serwerem.
|
||||
# Na przykład, możesz wpisać "mc.hypixel.net" lub bezpośredni adres IP, taki jak "1.1.1.1".
|
||||
destination-host: "play.my-server.com"
|
||||
# Jaki port powinien używać Sonar podczas przenoszenia gracza do serwera początkowego?
|
||||
# Jeśli serwer nie potrzebuje portu do połączenia, można pozostawić 25565.
|
||||
destination-port: 25565
|
||||
|
||||
# Tryb gry gracza (gamemode) podczas weryfikacji
|
||||
# Dostępne rodzaje: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: Wszystkie komponenty interfejsu są widoczne
|
||||
# - CREATIVE: pasek życia i głodu jest ukryty
|
||||
# - ADVENTURE: wszystkie komponenty interfejsu są widoczne
|
||||
gamemode: ADVENTURE
|
||||
# Sonar buforuje większość pakietów, które wysyła do klienta, w tym pakiet LoginSuccess.
|
||||
# Jakiej nazwy użytkownika powinien używać Sonar dla pakietu LoginSuccess?
|
||||
# Nazwa użytkownika nie będzie widoczna dla gracza bez modyfikacji klienta
|
||||
cached-username: "Sonar"
|
||||
# Czy Sonar powinien rejestrować połączenia nowych graczy?
|
||||
log-connections: true
|
||||
# Czy Sonar powinien rejestrować nowe próby połączenia podczas ataku?
|
||||
log-during-attack: false
|
||||
# Czy sonar powinien zapisywać każdy ruch/zmianę pozycji podczas weryfikacji?
|
||||
# Nie jest to zalecane dla serwerów publicznych, ale może być pomocne w wykrywaniu błędów.
|
||||
debug-xyz-positions: false
|
||||
# Czy Sonar powinien również sprawdzać graczy Geyser (Bedrock)?
|
||||
# Ta funkcja jest eksperymentalna i może powodować problemy
|
||||
# Jeśli ta opcja jest wyłączona, gracze Bedrock będą pomijani
|
||||
check-geyser-players: true
|
||||
# Czas, jaki ma upłynąć od zatrzymania wysyłania pakietów przez gracza do wyrzucenia.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
read-timeout: 8000
|
||||
# Czas, jaki ma upłynąć od zatrzymania wysyłania pakietów przez serwer do wyrzucenia.
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
write-timeout: 10000
|
||||
# Jak długo niezweryfikowany gracz powinien czekać przed ponownym połączeniem do serwera?
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
rejoin-delay: 8000
|
||||
# Jak długo Sonar powinien zapamiętywać liczbę nieudanych weryfikacji gracza?
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
remember-time: 120000
|
||||
# Na jak długo Adres IP ma zostać umieszczony na czarnej liście, jeśli przekroczył liczbe nieudanych weryfikacji?
|
||||
# (Wartość ta reprezentuje czas w milisekundach: 1 sekunda = 1000 milisekund)
|
||||
blacklist-time: 600000
|
||||
# Jak często gracz ma nie przechodzić weryfikacji, aby znaleźć się na czarnej liście?
|
||||
# (Ustaw tę wartość na 0, aby całkowicie wyłączyć czarną listę).
|
||||
blacklist-threshold: 2
|
||||
# Lista protokołów, które nie mogą w ogóle dołączyć do serwera
|
||||
# Pełną listę wszystkich protokołów można znaleźć tutaj:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Na przykład Minecraft 1.20 ma protokół 763.
|
||||
blacklisted-protocols: []
|
||||
@@ -80,26 +80,24 @@ verification:
|
||||
gravity:
|
||||
# Должен ли Sonar проверять наличие действительной гравитации клиента? (Рекомендуется)
|
||||
enabled: true
|
||||
# Должен ли Sonar проверять, сталкивается ли игрок с блоками? (Рекомендуется)
|
||||
check-collisions: true
|
||||
# Должен ли Sonar заставлять игрока решать CAPTCHA, если он не проходит проверку на гравитацию?
|
||||
captcha-on-fail: false
|
||||
# Максимальное количество тиков, на которое должен упасть игрок, чтобы ему было разрешено упасть на платформу
|
||||
max-movement-ticks: 8
|
||||
# Режим игрока во время проверки
|
||||
# Возможные типы: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: все компоненты пользовательского интерфейса видны
|
||||
# - CREATIVE: здоровье и голод скрыты
|
||||
# - ADVENTURE: все компоненты пользовательского интерфейса видны
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# Проверяет, сталкивается ли игрок с блоками, порожденными под ним
|
||||
collision:
|
||||
# Должен ли Sonar проверять, сталкивается ли игрок с блоками? (Рекомендуется)
|
||||
enabled: true
|
||||
# Какой тип блока следует использовать в Sonar для платформы столкновений?
|
||||
# Возможные типы: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# Рекомендуется редактировать только если вы знаете, что делаете
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# Проверяет, отправляет ли клиент правильные пакеты при садке на транспортное средство
|
||||
vehicle:
|
||||
# Возможные типы: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: Новые игроки всегда будут садиться на транспортное средство (Рекомендуется)
|
||||
# - DURING_ATTACK: Новые игроки будут садиться на транспортное средство только во время атаки
|
||||
# - NEVER: Новые игроки никогда не будут садиться на транспортное средство
|
||||
timing: ALWAYS
|
||||
# Должен ли сонар проверять, правильно ли игрок входит в лодку?
|
||||
enabled: true
|
||||
|
||||
# Заставить игрока ввести код с картинки в чат
|
||||
map-captcha:
|
||||
@@ -176,6 +174,7 @@ verification:
|
||||
# Если включено, игрок будет возвращен на исходный сервер
|
||||
# после успешного прохождения проверки на бота.
|
||||
# Эта функция была введена Mojang в версии Minecraft 1.20.5
|
||||
# Учебник: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Должен ли Sonar переместить игрока на исходный сервер (вместо выкидывания его)?
|
||||
# Для этого необходимо включить функцию в конфигурации вашего сервера
|
||||
@@ -191,6 +190,16 @@ verification:
|
||||
# Если ваш сервер не требует порта для подключения, вы можете оставить это как 25565.
|
||||
destination-port: 25565
|
||||
|
||||
# Режим игрока во время проверки
|
||||
# Возможные типы: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: все компоненты пользовательского интерфейса видны
|
||||
# - CREATIVE: здоровье и голод скрыты
|
||||
# - ADVENTURE: все компоненты пользовательского интерфейса видны
|
||||
gamemode: ADVENTURE
|
||||
# Sonar кэширует большинство пакетов, которые он отправляет клиенту, включая пакет LoginSuccess.
|
||||
# Какое имя пользователя должен использовать Sonar для пакета LoginSuccess?
|
||||
# Имя пользователя не будет видно игроку без модификации клиента
|
||||
cached-username: "Sonar"
|
||||
# Должен ли Sonar регистрировать новые попытки проверки?
|
||||
log-connections: true
|
||||
# Должен ли Sonar регистрировать новые попытки проверки во время атак?
|
||||
@@ -220,10 +229,8 @@ verification:
|
||||
# Как часто игрок должен проваливать проверку, чтобы попасть в черный список?
|
||||
# (Установите это значение на 0, чтобы полностью отключить черный список)
|
||||
blacklist-threshold: 2
|
||||
# Список протоколов, которые не проверяются Sonar (обход проверки)
|
||||
# Список протоколов, которые вообще не могут присоединиться к серверу
|
||||
# Полный список всех идентификаторов протоколов можно найти здесь:
|
||||
# https://wiki.vg/Protocol_version_numbers
|
||||
# Например, Minecraft 1.20 имеет ID 763.
|
||||
whitelisted-protocols: []
|
||||
# Список протоколов, которые вообще не могут присоединиться к серверу
|
||||
blacklisted-protocols: []
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
# 未归类的设置
|
||||
general:
|
||||
# Sonar 是否应该自动检查新的更新?
|
||||
# Sonar 是否应该自动检查新的更新?
|
||||
# Sonar 使用 GitHub API 检查当前版本是否过时
|
||||
check-for-updates: true
|
||||
# Sonar 应该在控制台中打印玩家的 IP 地址吗?
|
||||
@@ -78,26 +78,24 @@ verification:
|
||||
gravity:
|
||||
# Sonar 是否应该检查玩家的下落移动? (推荐启用)
|
||||
enabled: true
|
||||
# Sonar 是否应该生成方块平台以检查玩家下落与方块之间的碰撞? (推荐启用)
|
||||
check-collisions: true
|
||||
# 当玩家验证失败时 是否向他们发送验证码?
|
||||
captcha-on-fail: false
|
||||
# 玩家跌落在平台上多少tick后才能被算作站在平台上?
|
||||
max-movement-ticks: 8
|
||||
# 玩家在虚拟服务器中的游戏模式
|
||||
# 可用类型: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: 生存模式, 所有UI可见
|
||||
# - CREATIVE: 创造模式, 隐藏血量和饱食度
|
||||
# - ADVENTURE: 冒险模式, 所有UI可见
|
||||
gamemode: ADVENTURE
|
||||
|
||||
# 检查玩家是否与下方生成的方块发生碰撞
|
||||
collision:
|
||||
# Sonar 是否应该生成方块平台以检查玩家下落与方块之间的碰撞? (推荐启用)
|
||||
enabled: true
|
||||
# Sonar 应该使用哪种类型的块作为碰撞平台?
|
||||
# 可用类型: STONE, BARRIER, TRAPDOOR, ENCHANTMENT_TABLE
|
||||
# 只有当您知道您在做什么时, 才应该编辑它.
|
||||
collision-block-type: TRAPDOOR
|
||||
|
||||
# 检查客户端坐在实体上时是否发送了正确的数据包
|
||||
vehicle:
|
||||
# 可用类型: ALWAYS, DURING_ATTACK, NEVER
|
||||
# - ALWAYS: 始终应用该检查
|
||||
# - DURING_ATTACK: 仅在疑似遭受攻击时应用该检查
|
||||
# - NEVER: 永远不应用该检查
|
||||
timing: ALWAYS
|
||||
# 声纳是否应该检查玩家是否正确进入船只?
|
||||
enabled: true
|
||||
|
||||
# 地图验证码
|
||||
map-captcha:
|
||||
@@ -143,7 +141,8 @@ verification:
|
||||
auto-color: false
|
||||
# Sonar 应该一次准备多少个验证码?
|
||||
precompute: 500
|
||||
# Sonar 应该给玩家多少时间完成验证码? (该值为毫秒. 1秒等于1000毫秒.)
|
||||
# Sonar 应该给玩家多少时间完成验证码?
|
||||
# (该值为毫秒. 1秒等于1000毫秒.)
|
||||
max-duration: 30000
|
||||
# 玩家最多能输入多少次错误的验证码?
|
||||
max-tries: 3
|
||||
@@ -171,6 +170,7 @@ verification:
|
||||
|
||||
# 开启后 玩家将在验证成功后被重定向到指定的服务器
|
||||
# 该功能由 Mojang 在 Minecraft 1.20.5 版本中引入
|
||||
# 教程: https://youtu.be/aSWrzQVjGnc?t=71
|
||||
transfer:
|
||||
# Sonar 是否应该将玩家重定向到指定的服务器而不是踢出玩家?
|
||||
# 为此您必须在服务器软件的配置文件中允许接受来自transfer的连接.
|
||||
@@ -185,6 +185,16 @@ verification:
|
||||
# 如果服务器地址不包含端口 请保留25565.
|
||||
destination-port: 25565
|
||||
|
||||
# 玩家在虚拟服务器中的游戏模式
|
||||
# 可用类型: SURVIVAL, CREATIVE, ADVENTURE
|
||||
# - SURVIVAL: 生存模式, 所有UI可见
|
||||
# - CREATIVE: 创造模式, 隐藏血量和饱食度
|
||||
# - ADVENTURE: 冒险模式, 所有UI可见
|
||||
gamemode: ADVENTURE
|
||||
# Sonar 缓存它发送到客户端的大部分数据包, 包括 LoginSuccess 数据包.
|
||||
# Sonar 应该使用哪个用户名来接收 LoginSuccess 数据包?
|
||||
# 如果不修改客户端, 用户名将不会对玩家可见
|
||||
cached-username: "Sonar"
|
||||
# 当 Sonar 尝试验证玩家时 是否在控制台中打印它?
|
||||
log-connections: true
|
||||
# Sonar 是否应该记录在攻击期间内的验证会话?
|
||||
@@ -213,9 +223,7 @@ verification:
|
||||
blacklist-time: 600000
|
||||
# 玩家必须验证失败多少次才会被列入黑名单? (设置为0以完全禁用)
|
||||
blacklist-threshold: 2
|
||||
# Sonar 不应该检查哪些协议? (即 跳过检查)
|
||||
# 黑名单中的协议将阻止所有使用该协议的客户端.
|
||||
# 您可以在 https://wiki.vg/Protocol_version_numbers 找到所有协议号
|
||||
# 例如 Minecraft 1.20 对应的协议号为763.
|
||||
whitelisted-protocols: []
|
||||
# 黑名单中的协议将阻止所有使用该协议的客户端.
|
||||
blacklisted-protocols: []
|
||||
@@ -0,0 +1,288 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# Potřebujete pomoc nebo máte otázky? https://jonesdev.xyz/discord
|
||||
# Zdrojový kód: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licencováno pod GPL-3.0
|
||||
#
|
||||
|
||||
# Zástupný text pro každý '<prefix>' v tomto konfiguračním souboru
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Zástupný text pro každou '<support-url>' v tomto konfiguračním souboru
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Zástupný text pro každý '<header>' v tomto konfiguračním souboru
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Zástupný text pro každý '<footer>' v tomto konfiguračním souboru
|
||||
footer:
|
||||
- <gray>Pokud se domníváte, že jde o chybu, kontaktujte administrátora.
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale je příliš mnoho online hráčů s jejich IP adresou
|
||||
too-many-online-per-ip:
|
||||
- '<header>'
|
||||
- <red>Je příliš mnoho hráčů online s vaší IP adresou.
|
||||
- '<footer>'
|
||||
|
||||
# Překlady pro příkazy
|
||||
commands:
|
||||
# Zpráva zobrazená, když se hráč pokusí spustit /sonar bez oprávnění
|
||||
no-permission: '<prefix><red>Nemáte oprávnění k provedení tohoto příkazu.'
|
||||
# Zpráva zobrazená, když někdo použije příkaz nesprávně
|
||||
incorrect-usage: '<prefix><red>Použití: /sonar <subcommand-usage>'
|
||||
# Zpráva zobrazená, když někdo zadá neplatnou IP adresu (Neplatný formát)
|
||||
invalid-ip-address: '<prefix>IP adresa, kterou jste zadali, se zdá být neplatná.'
|
||||
# Zpráva zobrazená, když konzole spustí příkaz, který je pouze pro hráče
|
||||
player-only: '<prefix><red>Tento příkaz můžete provést pouze jako hráč.'
|
||||
# Zpráva zobrazená, když hráč spustí příkaz, který je pouze pro konzoli
|
||||
console-only: '<prefix><red>Z bezpečnostních důvodů můžete tento příkaz provést pouze přes konzoli.'
|
||||
# Zpráva zobrazená, když hráč nemá oprávnění k provedení určitého podpříkazu
|
||||
subcommand-no-permission: '<prefix><red>Nemáte oprávnění k provedení tohoto podpříkazu. <gray>(<permission>)'
|
||||
|
||||
# Překlady pro '/sonar'
|
||||
main:
|
||||
# Informační zpráva, která se zobrazí nad vším při spuštění hlavního příkazu
|
||||
header:
|
||||
- <yellow>Běží <b>Sonar</b> <version> na <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development a přispěvatelé Sonaru</click>
|
||||
- ''
|
||||
- <gray>Potřebujete pomoc nebo máte nějaké otázky?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Kliknutím otevřete Discord)'>Otevřete ticket na Discordu </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Kliknutím otevřete GitHub)'>nebo otevřete nový problém na GitHubu.
|
||||
- ''
|
||||
# Formátování seznamu podpříkazů zobrazených při spuštění hlavního příkazu
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Pouze hráči: </gray><only-players><br><gray>Vyžaduje konzoli: </gray><only-console><br><gray>Oprávnění: </gray><white><permission><br><gray>Aliasy: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formátování pro hodnoty ano (zaškrtnutí) a ne (křížek) v textu po najetí myší na podpříkazy
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Překlady pro '/sonar reload'
|
||||
reload:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Znovu načíst všechny konfigurace"
|
||||
# Zpráva zobrazená, když někdo začne znovu načítat Sonar
|
||||
start: '<prefix>Znovu načítám Sonar...'
|
||||
# Zpráva zobrazená, když Sonar dokončil znovu načítání
|
||||
finish: '<prefix><green>Úspěšně znovu načteno <gray>(<time-taken>ms)'
|
||||
|
||||
# Překlady pro '/sonar verbose'
|
||||
verbose:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Povolit nebo zakázat podrobný výpis Sonaru"
|
||||
# Zpráva zobrazená, když se hráč přihlásí k odběru podrobného výpisu Sonaru
|
||||
subscribe: '<prefix>Nyní zobrazujete podrobný výpis Sonaru.'
|
||||
# Zpráva zobrazená, když se hráč odhlásí z odběru podrobného výpisu Sonaru
|
||||
unsubscribe: '<prefix>Již nezobrazujete podrobný výpis Sonaru.'
|
||||
|
||||
# Překlady pro '/sonar notify'
|
||||
notify:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Povolit nebo zakázat oznámení o útocích"
|
||||
# Zpráva zobrazená, když se hráč přihlásí k odběru oznámení o útocích Sonaru
|
||||
subscribe: '<prefix>Nyní zobrazujete oznámení o útocích Sonaru.'
|
||||
# Zpráva zobrazená, když se hráč odhlásí z odběru oznámení o útocích Sonaru
|
||||
unsubscribe: '<prefix>Již nezobrazujete oznámení o útocích Sonaru.'
|
||||
|
||||
# Překlady pro '/sonar blacklist'
|
||||
blacklist:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Spravovat IP adresy na černé listině"
|
||||
# Zpráva zobrazená, když se někdo pokusí vymazat černou listinu, ale je prázdná
|
||||
empty: '<prefix>Černá listina je momentálně prázdná. Proto nebyly z černé listiny odstraněny žádné IP adresy.'
|
||||
# Zpráva zobrazená, když někdo vymaže černou listinu
|
||||
clear: '<prefix>Úspěšně jste odstranili celkem <amount> IP adres(y) z černé listiny.'
|
||||
# Zpráva zobrazená, když někdo zkontroluje velikost černé listiny
|
||||
size: '<prefix>Černá listina momentálně obsahuje <amount> IP adres(y).'
|
||||
# Zpráva zobrazená, když někdo odstraní IP adresu z černé listiny
|
||||
remove: '<prefix>Úspěšně odstraněno <ip> z černé listiny.'
|
||||
# Zpráva zobrazená, když někdo přidá IP adresu na černou listinu
|
||||
add: '<prefix>Úspěšně přidáno <ip> na černou listinu.'
|
||||
# Zpráva zobrazená, když někdo přidá IP adresu na černou listinu, která je ověřená
|
||||
add-warning: '<prefix><red>Varování: <white><ip> je momentálně na bílé listině. Zvažte odstranění IP adresy ze seznamu ověřených hráčů, abyste se vyhnuli potenciálním problémům.'
|
||||
# Zpráva zobrazená, když někdo přidá IP adresu na černou listinu, ale již je na černé listině
|
||||
ip-duplicate: '<prefix>IP adresa, kterou jste zadali, je již na černé listině.'
|
||||
# Zpráva zobrazená, když někdo odstraní IP adresu z černé listiny, ale není na černé listině
|
||||
ip-not-found: '<prefix>IP adresa, kterou jste zadali, není na černé listině.'
|
||||
|
||||
# Překlady pro '/sonar verified'
|
||||
verified:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Spravovat ověřené IP adresy"
|
||||
# Zpráva zobrazená, když se někdo pokusí vymazat seznam ověřených hráčů, ale je prázdný
|
||||
empty: '<prefix>Seznam ověřených hráčů je momentálně prázdný. Proto nebyli žádní hráči odebráni z ověření.'
|
||||
# Zpráva zobrazená, když někdo vymaže seznam ověřených hráčů
|
||||
clear: '<prefix>Úspěšně jste odebrali ověření celkem <amount> unikátním hráčům.'
|
||||
# Zpráva zobrazená, když někdo zkontroluje velikost seznamu ověřených hráčů
|
||||
size: '<prefix>Momentálně je ověřeno <amount> unikátních hráčů.'
|
||||
# Zpráva zobrazená, když někdo odebere ověření IP adrese
|
||||
remove: '<prefix>Úspěšně odebráno ověření pro <ip>.'
|
||||
# Zpráva zobrazená, když někdo ověří IP adresu
|
||||
add: '<prefix>Úspěšně ověřeno <ip>.'
|
||||
# Zpráva zobrazená, když se někdo pokusí ověřit IP adresu/UUID, které jsou již ověřeny
|
||||
already: '<prefix>IP adresa a UUID, které jste zadali, jsou již ověřeny.'
|
||||
# Zpráva zobrazená, když někdo odebere ověření IP adrese, ale není ověřena
|
||||
ip-not-found: '<prefix>IP adresa, kterou jste zadali, není ověřena.'
|
||||
# Zpráva zobrazená, když někdo vyhledá historii ověření IP adresy
|
||||
history: '<prefix><yellow>Předchozí UUID pro <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Překlady pro '/sonar statistics'
|
||||
statistics:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Zobrazit statistiky relace tohoto serveru"
|
||||
# Informační zpráva, která se zobrazí nad vším při zobrazení statistik
|
||||
header: '<prefix>Zobrazuji <statistics-type> statistiky pro tuto relaci:'
|
||||
# Zpráva zobrazená, když se hráč pokusí zobrazit neznámou statistiku
|
||||
unknown-type: '<prefix><red>Neznámý typ statistiky! Dostupné statistiky: <gray><statistics>'
|
||||
# Formát zprávy obecných statistik
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Ověřené IP adresy: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Ověřující se IP adresy: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>IP adresy na černé listině: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Aktuálně ve frontě přihlášení: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Celkový počet ne-unikátních připojení: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Celkový počet pokusů o ověření: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Celkový počet neúspěšných ověření: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Doba provozu serveru: <white><server-uptime>'
|
||||
# Formát zprávy statistik CPU
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Využití CPU procesem právě teď: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Využití CPU systémem právě teď: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Využití CPU procesem na jádro: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Využití CPU systémem na jádro: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Celkový počet virtuálních CPU: <white><virtual-core-count>'
|
||||
# Formát zprávy statistik paměti
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Celková volná paměť: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Celková použitá paměť: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Celková maximální paměť: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Celková alokovaná paměť: <white><total-memory>'
|
||||
# Formát zprávy statistik sítě
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Aktuální příchozí využitá šířka pásma: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Aktuální odchozí využitá šířka pásma: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Celková příchozí využitá šířka pásma: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Celková odchozí využitá šířka pásma: <white><incoming-traffic-ttl>'
|
||||
|
||||
# Překlady pro '/sonar dump'
|
||||
dump:
|
||||
# Popis tohoto podpříkazu
|
||||
description: "Vypsat informace pro vývojáře"
|
||||
# Formát informací odeslaných do konzole
|
||||
log: "Vygenerovaný výpis: <dumped-json-data>"
|
||||
|
||||
# Překlady pro všechny zprávy během procesu ověření
|
||||
verification:
|
||||
# Překlady pro všechny ladící zprávy během ověření
|
||||
logs:
|
||||
# Zpráva zaznamenána do konzole, kdykoli se nový hráč připojí k serveru
|
||||
connection: '<username><ip> (<protocol>) se připojil.'
|
||||
# Zpráva zaznamenána do konzole, kdykoli hráč selže při ověření
|
||||
failed: '<ip> (<protocol>) neprošel kontrolou bota z důvodu: <reason>'
|
||||
# Zpráva zaznamenána do konzole, kdykoli je hráč zařazen na černou listinu
|
||||
blacklisted: '<ip> (<protocol>) byl zařazen na černou listinu kvůli příliš mnoha neúspěšným pokusům'
|
||||
# Zpráva zaznamenána do konzole, kdykoli je hráč ověřen
|
||||
successful: '<username> byl úspěšně ověřen (<time-taken>s!).'
|
||||
|
||||
# Zprávy, které se zobrazují hráči, když má zadat CAPTCHA
|
||||
captcha:
|
||||
# Zpráva zobrazená hráči, když má zadat odpověď na captchu
|
||||
enter-code: '<prefix><green>Prosím, zadejte do chatu kód, který je zobrazen na mapě.'
|
||||
# Zpráva zobrazená hráči, když zadá špatnou odpověď do chatu
|
||||
incorrect: '<prefix><red>Zadali jste špatný kód. Zkuste to prosím znovu.'
|
||||
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, zatímco captcha ještě není připravena
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <yellow>Vaše anti-bot data ještě nejsou připravena.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo znovu připojí příliš rychle během ověření
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <gold>Připojili jste se příliš rychle, zkuste to později.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale již probíhá jeho ověření
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <red>Vaše IP adresa je právě ověřována.
|
||||
- <red>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale je již ve frontě na ověření
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <red>Vaše IP adresa je momentálně ve frontě na ověření.
|
||||
- <red>Počkejte prosím několik minut, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale je dočasně na černé listině
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <red>Momentálně vám je odepřen vstup na server.
|
||||
- <red>Počkejte prosím několik minut, abyste se mohli znovu připojit k serveru.
|
||||
- <gold>Falešně pozitivní? <gray><support-url>
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí s neplatným uživatelským jménem
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <red>Vaše uživatelské jméno obsahuje neplatné znaky.
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí s verzí na černé listině
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <red>Používáte verzi, která není na našem serveru povolena.
|
||||
- <gold>Potřebujete pomoc s přihlášením? <gray><support-url>
|
||||
- '<footer>'
|
||||
# Zpráva o odpojení, která se zobrazí, když někdo úspěšně projde ověřením
|
||||
success:
|
||||
- '<header>'
|
||||
- <green>Úspěšně jste prošli ověřením.
|
||||
- <white>Nyní se můžete připojit k serveru po opětovném připojení.
|
||||
# Zpráva o odpojení, která se zobrazí, když někdo selže při ověření
|
||||
failed:
|
||||
- '<header>'
|
||||
- <red>Neprošli jste ověřením bota.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- <gold>Potřebujete pomoc s přihlášením? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Překlady pro podrobné zprávy Sonaru v panelu akcí
|
||||
verbose:
|
||||
layout:
|
||||
# Rozložení pro podrobné zprávy Sonaru v panelu akcí
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Přihlášení/s <white><logins-per-second> <dark_aqua>╺ <gray>Ověřování <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Rozložení pro podrobné zprávy Sonaru v panelu akcí během útoku
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Přihlášení/s <white><logins-per-second> <dark_aqua>╺ <gray>Fronta <white><queued> <dark_aqua>╺ <gray>Ověřování <white><verifying> <dark_aqua>╺ <gray>Trvání <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animace pro panel akcí
|
||||
# Alternativy:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
# - ⬈, ⬊, ⬋, ⬉
|
||||
animation:
|
||||
- ◜
|
||||
- ◝
|
||||
- ◞
|
||||
- ◟
|
||||
|
||||
# Překlady pro oznámení o útoku Sonaru
|
||||
notifications:
|
||||
# Rozložení pro titulek oznámení o útoku Sonaru
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# Rozložení pro podtitulek oznámení o útoku Sonaru
|
||||
subtitle: <gray>Byl detekován útok botů
|
||||
# Rozložení pro oznámení o útoku Sonaru v chatu
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Byl detekován útok botů.
|
||||
- ' <dark_aqua>▪ <gray>Přihlášení za sekundu: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Ověřující se hráči: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Hráči ve frontě: <white><queued>'
|
||||
- ''
|
||||
- <gray>Zobrazit více informací pomocí <white><click:run_command:'/sonar verbose'><hover:show_text:'(Klikněte pro spuštění)'>/sonar verbose</hover></click><gray> nebo <white><click:run_command:'/sonar stats'><hover:show_text:'(Klikněte pro spuštění)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -0,0 +1,289 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# გჭირდება დახმარება ან გაქვს კითხვები? დაგვიკავშირდი https://jonesdev.xyz/discord
|
||||
# კოდის წყარო: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# ცვლადი ყველა '<prefix>' სთვის ამ ფაილში.
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# ცვლადი ყველა '<support-url>' სთვის ამ ფაილში.
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# ცვლადი ყველა '<header>' სთვის ამ ფაილში.
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# ცვლადი ყველა '<footer>' სთვის ამ ფაილში.
|
||||
footer:
|
||||
- <gray>თუ გჯერათ რომ შეცდომა მოხდა, დაუკავშირდით ადმინისტრატორს.
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როდესაც ვინმე შემოვა, მაგრამ სერვერზე ბევრი მოთამაშეა მისი IP მისამართით
|
||||
too-many-online-per-ip:
|
||||
- '<header>'
|
||||
- <red>ძალიან ბევრი ადამიანია თქვენი IP მისამართით შემოსული.
|
||||
- '<footer>'
|
||||
|
||||
# თარგმანი ბრძანებებისთვის
|
||||
commands:
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც მოთამაშე ცდის /sonar ს ამის უფლების გარეშე
|
||||
no-permission: '<prefix><red>თქვენ არ გაქვთ უფლება ამ ბრძანების გასაცემად.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც ბრძანება არასწორად იქნება გაცემული.
|
||||
incorrect-გამოყენება: '<prefix><red>გამოყენება: /sonar <subcommand-გამოყენება>'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც ვინმე გვაწვდის არასწორ IP მისამართს (არასწორი ფორმატით)
|
||||
invalid-ip-address: '<prefix>თქვენი IP მისამართი, როგორც ჩანს არასწორია.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც კონსოლი ცდის იმ ბრძანების გაცემას რომლის გაცემაც მხოლოდ მოთამაშეს შეუძლია.
|
||||
player-only: '<prefix><red>თქვენ შეგიძლიათ ამ ბრძანების გაცემა მხოლოდ, როგორც მოთამაშეს.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც მოთამაშე გასცემს ბრძანებას რომლის გაცემაც მხოლოდ კონსოლს შეუძლია.
|
||||
console-only: '<prefix><red>უსაფრთხოების მიზეზების გამო, ამ ბრძანების შესრულება შეგიძლიათ მხოლოდ კონსოლიდან.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როდესაც მოთამაშე ცდის იმ ქვე-ბრძანების გაცემას რომლის უფლებაც არ აქვს.
|
||||
subcommand-no-permission: '<prefix><red>თქვენ არ გაქვთ უფლება რომ გასცეთ ეს ქვე-ბრძანება. <gray>(<permission>)'
|
||||
|
||||
# თარგმანი ბრძანება '/sonar' ისთვის
|
||||
main:
|
||||
# ინფორმაციული მესიჯი რომელიც იქნება ნაჩვენები ყველაფრის ზემოთ როდესაც ბრძანება გაიცემა.
|
||||
header:
|
||||
- <yellow>გამოიყენება <b>Sonar</b> <version> <platform>_ზე.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>გჭირდებათ დახმარება ან გაქვთ რამე კითხვა?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(დააჭირეთ რომ გახსნათ Discord)'>გახსენით ბილეთი Discord_ზე </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(დააჭირეთ რომ გახსნათ GitHub)'>ან შექმენით ახალი პრობლემის პოსტი GitHub_ზე.
|
||||
- ''
|
||||
# ქვე-ბრძანებების ფორმატი რომელიც არის ნაჩვენები მთავარი ბრძანების გაცემის დროს.
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>მხოლოდ მოთამაშეები: </gray><only-players><br><gray>აუცილებელია კონსოლი: </gray><only-console><br><gray>უფლება: </gray><white><permission><br><gray>სხვა-სახელი: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <ქვე-ბრძანება> <white><description></hover></suggest-subcommand>'
|
||||
# ფორმატი კი_სთვის (სტიჩკა) და არა_სთვის (ჯვარი) (გამოიყენება ქვე-ბრძანებებში)
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# თარგმანი '/sonar reload' სთვის
|
||||
reload:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "ხელახლა იტვირთება ყველა კონფიკურაცია"
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ხელახლა ჩატვირთავს სონარს
|
||||
start: '<prefix>ხელახლა იტვირთება Sonar...'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა Sonar_ი დაამთავრებს ხელახლა ჩატვირთვას
|
||||
finish: '<prefix><green>ხელახლა ჩატვირთვა წარმატებით მოხერხდა <gray>(<time-taken>ms)'
|
||||
|
||||
# თარგმანი '/sonar verbose' სთვის
|
||||
verbose:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "ჩართე ან გამორთე Sonar_ის დეტალური ინფორმაციის ჩვენება"
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა მოთამაშე ბრძანებს Sonarს დეტალური ინფორმაციის ჩვენებას
|
||||
subscribe: '<prefix>თქვენ ამიერიდან ხედავთ Sonar_ის დეტალურ ინფორმაციას.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ბრძანებს რომ აღარ დაინახოს დეტალური ინფორმაცია
|
||||
unsubscribe: '<prefix>თქვენ ვეღარ იხილავთ Sonar_ის დეტალურ ინფორმაციას.'
|
||||
|
||||
# თარგმანი '/sonar notify' სთვის
|
||||
notify:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "ჩართე ან გამორთე შეტევის ცნობები"
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა მოთამაშე ბრძანებს რომ დაინახოს შეტევების ცნობები
|
||||
subscribe: '<prefix>თქვენ ამიერიდან ხედავთ Sonarს შეტევის ცნობებს.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა მოთამაშე ბრძანებს რომ აღარ დაინახოს შეტევების ცნობები
|
||||
unsubscribe: '<prefix>თქვენ ვეღარ იხილავთ Sonarს შეტევის ცნობებს.'
|
||||
|
||||
# თარგმანი '/sonar blacklist' სთვის
|
||||
blacklist:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "შეცვალე აკრძალული (დაბლოკილი) IP მისამართები"
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის შავი ნუსხის გასუფთავებას თუმცა იგი ცარიელია.
|
||||
empty: '<prefix>შავი ნუსხა ამჟამად ცარიელია. ასე რომ არცერთი IP მისამართი არ მოშორებულა.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე გაასუფთავებს შავ ნუსხას.
|
||||
clear: '<prefix>თქვენ წარმატებით გაასუფთავეთ <amount> IP მისამართ(ებ)ი შავი ნუსხიდან.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე შეამოწმებს შავი ნუსხის ზომას.
|
||||
size: '<prefix>ამჟამად შავი ნუსხა შეიცავს <amount> IP მისამართ(ებ)ს.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოაშორებს IP მისამართს შავი ნუსხიდან.
|
||||
remove: '<prefix>თქვენ წარმატებით მოაშორეთ <ip> შავი ნუსხიდან.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე დაამატებს IP მისამართს შავ ნუსხაში.
|
||||
add: '<prefix>თქვენ წარმატებით დაამატეთ <ip> შავ ნუსხაში.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე დაამატებს IP მისამართს შავ ნუსხაში მაგრამ ეს მისამართი ვერიფიცირებულია
|
||||
add-warning: '<prefix><red>გაფრთხილება: <white><ip> ამჟამად არის თეთრ ნუსხაში. გთხოვთ მოაშოროთ ეს IP მისამართი თეთრი სიიდან რათა აიცილოთ თავიდან პოტენციური პრობლემები.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე დაამატებს IP მისამართს შავ ნუსხაში მაგრამ იგი უკვე არის იქ.
|
||||
ip-duplicate: '<prefix>ეს IP მისამართი ისედაც შავ ნუსხაშია.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოაშორებს IP მისამართს შავი ნუსხიდან მაგრამ იგი ისედაც არ არის იქ.
|
||||
ip-not-found: '<prefix>ეს IP მისამართი ისედაც არ არის შავ ნუსხაში.'
|
||||
|
||||
# თარგმანი '/sonar verified' სთვის
|
||||
verified:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "Manage verified IP მისამართები"
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის გაასუფთავოს ნუსხა შემოწმებული მოთამაშეებისა თუმცა იგი ისედაც ცარიელია.
|
||||
empty: '<prefix>ნუსხა შემოწმებული მოთამაშეებისა ისედაც ცარიელია. ასე რომ არ მოშორებულა არცერთი IP მისამართი იქიდან.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე გაასუფთავებს ნუსხას შემოწმებული მოთამაშეებისა.
|
||||
clear: '<prefix>თქვენ წარმატებით გაასუფთავეთ <amount> მოთამაშე შემოწმებულების ნუსხიდან.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე შეამოწმებს სიდიდეს შემოწმებული მოთამაშეების ნუსხისა.
|
||||
size: '<prefix>ამჟამად <amount> მოთამაშეა შემოწმებული.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოშლის IP მისამართს შემოწმებულების სიიდან.
|
||||
remove: '<prefix>წარმატებით მოშორდა <ip>.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე დაამატებს IP მისამართს შემოწმებულების ნუსხაში.
|
||||
add: '<prefix>Successfully verified <ip>.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე ცდის რომ დაამატოს IP/UUID შემოწმებულების ნუსხაში რაც უკვე დამატებულია.
|
||||
already: '<prefix>IP მისამართი/UUID რაც მოგვაწოდეთ ისედაც დამატებულია.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე მოშლის IP მისამართს შემოწმებულების ნუსხიდან მაგრამ ის იქ არ არის.
|
||||
ip-not-found: '<prefix>IP მისამართი ისედაც არ არის შემოწმებულების ნუსხაში.'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა ვინმე შეამოწმებს მოთამაშის შემოწმების ისტორიას.
|
||||
history: '<prefix><yellow>მანამდელი UUIDები <ip>_ის:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# თარგმანი '/sonar statistics' სთვის
|
||||
statistics:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "აჩვენე სერვერის ამჟამინდელი სტატისტიკები"
|
||||
# ინფორმაციული მესიჯი რაც არის ნაჩვენები ყველაფრის ზემოთ როცა ნახულობთ სტატისტიკებს.
|
||||
header: '<prefix>ვაჩვენებთ <statistics-type> სტატისტიკას:'
|
||||
# მესიჯი, რომელიც იქნება ნაჩვენები, როცა მოთამაშე ცდის იხილოს უცნობი სტატისტიკა.
|
||||
unknown-type: '<prefix><red>არასწორი სტატისტიკის ტიპი! ხელმისაწვდომი სტატისტიკები: <gray><statistics>'
|
||||
# ფორმატი სტატისტიკებისა
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>შემოწმებული IP მისამართები: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>მოწმდება IP მისამართები: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>შავ ნუსხაში შევიდა IP მისამართები: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>ამჟამად რიგში ჩაყენებული შემოსვლები: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში არა-unique შემოსვლები: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში შემოწმების მცდელობები: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში წარუმატებელი შემოწმებები: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>სერვერის ჩართვის დრო: <white><server-uptime>'
|
||||
# ფორმატი პროცესორის სტატისტიკისა
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>პროცესის CPU გამოყენება ზუსტად ახლა: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>სისტემის CPU გამოყენება ზუსტად ახლა: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>თითო-ბირთვის პროცესის CPU გამოყენება: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>თითო-ბირთვის სისტემის CPU გამოყენება: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში ვირტუალური CPU რაოდენობა: <white><virtual-core-count>'
|
||||
# ფორმატი სწრაფი მეხსიერებისა
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>ჯამში თავისუფალი სწრაფი მეხსიერება:<white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში გამოყენებული სწრაფი მეხსიერება:<white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში მაქსიმუმი სწრაფი მეხსიერება:<white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში გამოყოფილი სწრაფი მეხსიერება:<white><total-memory>'
|
||||
# ფორმატი ქსელის სტატისტიკისა
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>ამჟამინდელი მომდინარე გამოყენებული ქსელის გამოყენება: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>ამჟამინდელი გამდინარე გამოყენებული ქსელის გამოყენება: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში მომდინარე გამოყენებული ქსელის გამოყენება: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>ჯამში გამდინარე გამოყენებული ქსელის გამოყენება: <white><incoming-traffic-ttl>'
|
||||
|
||||
# თარგმანი '/sonar dump' სთვის
|
||||
dump:
|
||||
# აღწერა ამ ქვე-ბრძანებისთვის
|
||||
description: "Print developer information"
|
||||
# ფორმატი ინფორმაციისთვის რაც კონსოლს ეგზავნება
|
||||
log: "გენერირდა: <dumped-json-data>"
|
||||
|
||||
# თარგმანი ყველა მესიჯისთვის შემოწმების პროცესის დროს
|
||||
verification:
|
||||
# თარგმანი ყველა დეტალური ინფორმაციის მესიჯებისა
|
||||
logs:
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა ახალი მოთამაშე შემოდის
|
||||
connection: '<username><ip> (<protocol>) დაკავშირდა.'
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა მოთამაშე წარუმატებლად ასრულებს შემოწმებას
|
||||
failed: '<ip> (<protocol>) წარუმატებლად გაირა შემოწმება: <reason>'
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა მოთამაშე შავ ნუსხაშია
|
||||
blacklisted: '<ip> (<protocol>) ბევრი წარუმატებელი შემოწმების გამო მოთამაშე შავ ნუსხაში შევიდა'
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა მოთამაშე შემოწმდა წარმატებულად
|
||||
successful: '<username> წარმატებულად შემოწმდა (<time-taken>s!).'
|
||||
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა დაწერონ CAPTCHA
|
||||
captcha:
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა მიუთითონ CAPTCHA ს პასუხი
|
||||
enter-code: '<prefix><green>გთხოვთ დაწეროთ ჩატში ის კოდი რომელიც არის ნაჩვენები რუკაზე.'
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა ისინი ჩაწერენ არასწორ პასუხს ჩატში
|
||||
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. გთხოვთ ხელახლა ცადოთ.'
|
||||
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა მაგრამ CAPTCHA არ არის მზად
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <yellow>ანტიბოტი ჯერ არ მომზადებულა.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა შემოხვალთ.
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ხელახლა შემოდის ძალიან სწრაფად შემოწმების დროს.
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <gold>თქვენ კავშირდებით ხელახლა ძალიან სწრაფად.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ცდის შემოსვლას თუმცა იგი უკვე მოწმდება
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <red>თქვენი IP მისამართი ამჟამად უკვე მოწმდება.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა იგი უკვე არის შემოწმების რიგში
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <red>თქვენი IP მისამართი უკვე არის რიგში შემოწმებისთვის.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა დროებით დაბლოკილია
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <red>თქვენ ამჟამად დაბლოკილი ხართ სერვერიდან.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი რომ ისევ შეგეძლოტ სერვერზე შემოსვლა.
|
||||
- <gold>შეცდომა? <gray><support-url>
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა არასწორი სახელით
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <red>შენი სახელი შეიცავს არასწორ კომპონენტებს.
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა დაბლოკილი ვერსიით
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <red>შენ იყენებ ვერსიას რომელიც არ არის დაშვებული სერვერზე.
|
||||
- <gold>გჭირდება დახმარება შესვლაში? <gray><support-url>
|
||||
- '<footer>'
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოწმდა წარმატებით
|
||||
success:
|
||||
- '<header>'
|
||||
- <green>თქვენ წარმატებით გაიარეთ შემოწმება.
|
||||
- <white>თქვენ უკვე შეგიძლიათ შეხვიდეთ სერვერზე.
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე წარუმატებლად გაივლის შემოწმებას
|
||||
failed:
|
||||
- '<header>'
|
||||
- <red>თქვენ ვერ გაიარეთ შემოწმება.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- <gold>გჭირდებათ დახმარება შესვლაში? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# თარგმანი Sonar_ის actionbar დეტალური ინფორმაციის მესიჯებისთვის
|
||||
verbose:
|
||||
layout:
|
||||
# ჩვენება Sonar_ის actionbar დეტალური ინფორმაციისთვის
|
||||
normal: '<prefix><gray>კთწ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: '<prefix><gray>კთწ <white><connections-per-second> <dark_aqua>╺ <gray>შემოსვლა/წ <white><logins-per-second> <dark_aqua>╺ <gray>რიგი <white><queued> <dark_aqua>╺ <gray>მოწმდება <white><verifying> <dark_aqua>╺ <gray>ხანგრძლივობა <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animation for the action bar
|
||||
# Alternatives:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
# - ⬈, ⬊, ⬋, ⬉
|
||||
animation:
|
||||
- ◜
|
||||
- ◝
|
||||
- ◞
|
||||
- ◟
|
||||
|
||||
# თარგმანი Sonar's შეტევის ცნობების მესიჯებისთვის
|
||||
notifications:
|
||||
# ჩვენება Sonar_ის შეტევის ცნობების სათაურისთვის
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# ჩვენება Sonar_ის შეტევის ცნობების ქვე-სათაურისთვის
|
||||
subtitle: <gray>დაფიქსირდა შეტევა
|
||||
# ჩვენება Sonar_ის შეტევის ცნობების ჩატისთვის
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>დაფიქსირდა შეტევა.
|
||||
- ' <dark_aqua>▪ <gray>შემოსვლა თითო წამში: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>მოწმდება: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>რიგის ზომა: <white><queued>'
|
||||
- ''
|
||||
- <gray>იხილეთ ინფორმაცია: <white><click:run_command:'/sonar verbose'><hover:show_text:'(დააჭირეთ შესასრულებლად)'>/sonar verbose</hover></click><gray> ან <white><click:run_command:'/sonar stats'><hover:show_text:'(დააჭირეთ შესასრულებლად)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# Potrzebujesz pomocy lub masz pytania? https://jonesdev.xyz/discord
|
||||
# Source code: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Placeholder for every '<prefix>' in this configuration file
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Placeholder for every '<support-url>' in this configuration file
|
||||
support-url: jhttps://jonesdev.xyz/discord/
|
||||
# Placeholder for every '<header>' in this configuration file
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Placeholder for every '<footer>' in this configuration file
|
||||
footer:
|
||||
- <gray>Jeśli uważasz, że jest to błąd, skontaktuj się z administratorem.
|
||||
# Disconnect message that is shown when someone joins, but there are too many online players with their IP address
|
||||
too-many-online-per-ip:
|
||||
- <header>
|
||||
- <red>Jest zbyt wielu graczy online z Twoim adresem IP.
|
||||
- <footer>
|
||||
|
||||
# Translations for commands
|
||||
commands:
|
||||
# Message shown when a player tries running /sonar without permission
|
||||
no-permission: <prefix><red>Nie masz uprawnień do wykonania tego polecenia.
|
||||
# Message shown when someone uses a command incorrectly
|
||||
incorrect-usage: '<prefix><red>Użyj: /sonar <subcommand-usage>'
|
||||
# Message shown when someone provides an invalid IP address (Invalid format)
|
||||
invalid-ip-address: <prefix>Podany adres IP wydaje się być nieprawidłowy.
|
||||
# Message shown when the console runs a command that is player-only
|
||||
player-only: <prefix><red>Polecenie te może wykonać tylko jako gracz.
|
||||
# Message shown when a player runs a command that is console-only
|
||||
console-only: <prefix><red>Ze względów bezpieczeństwa polecenie to można wykonać tylko za pośrednictwem konsoli.
|
||||
# Message shown when a player does not have permission to execute a certain subcommand
|
||||
subcommand-no-permission: <prefix><red>Nie masz uprawnień do wykonania tej podkomendy. <gray>(<permission>)
|
||||
|
||||
# Translations for '/sonar'
|
||||
main:
|
||||
# Informational message that is shown above everything when running the main command
|
||||
header:
|
||||
- <yellow>Uruchomiony <b>Sonar</b> <version> na <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year>
|
||||
Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>Potrzebujesz pomocy lub masz pytania?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Kliknij aby otworzyć Discord)'>Otwórz zgłoszenie na Discordzie </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Kliknij aby otworzyć GitHub)'>lub otwórz nowe zgłoszenie w serwisie GitHub.
|
||||
- ''
|
||||
# Formatting of the list of subcommands shown when running the main command
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Only players: </gray><only-players><br><gray>Require
|
||||
console: </gray><only-console><br><gray>Permission: </gray><white><permission><br><gray>Aliases:
|
||||
</gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formatting for yes (tick) and no (cross) values in the subcommands hover text
|
||||
tick: <green>✔</green>
|
||||
cross: <red>✗</red>
|
||||
|
||||
# Translations for '/sonar reload'
|
||||
reload:
|
||||
# Description for this subcommand
|
||||
description: Przeładuj wszystkie konfiguracje
|
||||
# Message shown when someone starts reloading Sonar
|
||||
start: <prefix>Przeładowanie sonaru...
|
||||
# Message shown when Sonar has finished reloading
|
||||
finish: <prefix><green>Pomyślne przeładowanie <gray>(<time-taken>ms)
|
||||
|
||||
# Translations for '/sonar verbose'
|
||||
verbose:
|
||||
# Description for this subcommand
|
||||
description: Włączanie lub wyłączanie funkcji Sonar verbose
|
||||
# Message shown when a player subscribes to Sonar verbose
|
||||
subscribe: <prefix>Wyświetlana jest teraz pełna wersja programu Sonar.
|
||||
# Message shown when a player unsubscribes from Sonar verbose
|
||||
unsubscribe: <prefix>Sonar nie jest już wyświetlany w trybie verbose.
|
||||
|
||||
# Translations for '/sonar notify'
|
||||
notify:
|
||||
# Description for this subcommand
|
||||
description: Włączanie lub wyłączanie powiadomień o atakach
|
||||
# Message shown when a player subscribes to Sonar attack notifications
|
||||
subscribe: <prefix>Wyświetlane są teraz powiadomienia o atakach Sonar.
|
||||
# Message shown when a player unsubscribes from Sonar attack notifications
|
||||
unsubscribe: <prefix>Powiadomienia o atakach Sonar nie są już wyświetlane.
|
||||
|
||||
# Translations for '/sonar blacklist'
|
||||
blacklist:
|
||||
# Description for this subcommand
|
||||
description: Zarządzanie adresami IP z czarnej listy
|
||||
# Message shown when someone tries clearing the blacklist but is empty
|
||||
empty: <prefix>Czarna lista jest obecnie pusta. W związku z tym żadne adresy IP nie zostały usunięte z czarnej listy.
|
||||
# Message shown when someone clears the blacklist
|
||||
clear: <prefix>Udało Ci się usunąć łącznie <amount> adresów IP z czarnej listy.
|
||||
# Message shown when someone checks the size of the blacklist
|
||||
size: <prefix>Czarna lista zawiera obecnie <amount> adresów IP.
|
||||
# Message shown when someone removes an IP address from the blacklist
|
||||
remove: <prefix>Pomyślnie usunięto <ip> z czarnej listy.
|
||||
# Message shown when someone adds an IP address to the blacklist
|
||||
add: <prefix>Pomyślnie dodano <ip> do czarnej listy.
|
||||
# Message shown when someone adds an IP address to the blacklist that is verified
|
||||
add-warning: '<prefix><red>Ostrzeżenie: <white><ip> znajduje się obecnie na białej liście. Rozważ usunięcie adresu IP z listy zweryfikowanych graczy, aby uniknąć potencjalnych problemów.'
|
||||
# Message shown when someone adds an IP address to the blacklist, but it is already blacklisted
|
||||
ip-duplicate: <prefix>Podany adres IP znajduje się już na czarnej liście.
|
||||
# Message shown when someone removes an IP address from the blacklist, but it is not blacklisted
|
||||
ip-not-found: <prefix>Podany adres IP nie znajduje się na czarnej liście.
|
||||
|
||||
# Translations for '/sonar verified'
|
||||
verified:
|
||||
# Description for this subcommand
|
||||
description: Zarządzanie zweryfikowanymi adresami IP
|
||||
# Message shown when someone tries clearing the list of verified players but is empty
|
||||
empty: <prefix>Lista zweryfikowanych graczy jest obecnie pusta. W związku z tym żaden gracz nie został niezweryfikowany.
|
||||
# Message shown when someone clears the list of verified players
|
||||
clear: <prefix>Udało ci się zweryfikować łącznie <amount> unikalnych graczy.
|
||||
# Message shown when someone checks the size of the list of verified players
|
||||
size: <prefix>Obecnie zweryfikowanych jest <amount> unikalnych graczy.
|
||||
# Message shown when someone un-verifies an IP address
|
||||
remove: <prefix>Pomyślnie niezweryfikowany <ip>.
|
||||
# Message shown when someone verifies an IP address
|
||||
add: <prefix>Pomyślnie zweryfikowany <ip>.
|
||||
# Message shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: <prefix>Podany adres IP i identyfikator UUID zostały już zweryfikowane.
|
||||
# Message shown when someone un-verifies an IP address, but it is not verified
|
||||
ip-not-found: <prefix>Podany adres IP nie został zweryfikowany.
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>Poprzednie identyfikatory UUID dla <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Translations for '/sonar statistics'
|
||||
statistics:
|
||||
# Description for this subcommand
|
||||
description: Wyświetlanie statystyk sesji tego serwera
|
||||
# Informational message that is shown above everything when viewing the statistics
|
||||
header: '<prefix>Wyświetlanie <statistics-type> statystyki dla tej sesji:'
|
||||
# Message shown when a player tries viewing an unknown statistic
|
||||
unknown-type: '<prefix><red>Nieznany typ statystyk! Dostępne statystyki: <gray><statistics>'
|
||||
# Format of the general statistics message
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Zweryfikowane adresy IP: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Weryfikacja adresów IP: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Adresy IP na czarnej liście: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Aktualnie oczekujące logowania: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Łączna liczba nieunikalnych połączeń: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Łączna liczba prób weryfikacji: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Łączna liczba nieudanych weryfikacji: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Czas pracy serwera: <white><server-uptime>'
|
||||
# Format of the CPU statistics message
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Wykorzystanie procesora CPU w tej chwili: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Obecne użycie procesora systemowego: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Użycie procesora na rdzeń: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Użycie procesora na rdzeń systemu: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita liczba wirtualnych procesorów: <white><virtual-core-count>'
|
||||
# Format of the memory statistics message
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Całkowita ilość wolnej pamięci: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita wykorzystana pamięć: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita maksymalna pamięć: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita przydzielona pamięć: <white><total-memory>'
|
||||
# Format of the network statistics message
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Aktualnie używana przychodząca przepustowość: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Aktualnie używana przepustowość wychodząca: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita wykorzystana przepustowość przychodząca: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Całkowita wykorzystana przepustowość wychodząca: <white><incoming-traffic-ttl>'
|
||||
|
||||
# Translations for '/sonar dump'
|
||||
dump:
|
||||
# Description for this subcommand
|
||||
description: Drukowanie informacji o deweloperze
|
||||
# Format of the information sent to console
|
||||
log: 'Wygenerowany zrzut: <dumped-json-data>'
|
||||
|
||||
# Translations for all messages during the verification process
|
||||
verification:
|
||||
# Translations for all debug messages during the verification
|
||||
logs:
|
||||
# Message logged to console whenever a new player joins the server
|
||||
connection: <username><ip> (<protocol>) połączył się.
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '<ip> (<protocol>) nie powiodło się sprawdzenie bota dla: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
blacklisted: <ip> (<protocol>) został umieszczony na czarnej liście z powodu zbyt wielu nieudanych prób
|
||||
# Message logged to console whenever a player is verified
|
||||
successful: <username> został pomyślnie zweryfikowany (<time-taken>s!).
|
||||
|
||||
# Messages that are shown to the player when they are supposed to enter the CAPTCHA
|
||||
captcha:
|
||||
# Message shown to the player when they have to enter the answer to the captcha
|
||||
enter-code: <prefix><green>Wprowadź na czacie kod wyświetlany na mapie.
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: <prefix><red>Wprowadzono nieprawidłowy kod. Spróbuj ponownie.
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- <header>
|
||||
- <yellow>Twoje dane antybotowe nie zostały jeszcze przygotowane.
|
||||
- <gray>Odczekaj kilka sekund przed ponowną próbą weryfikacji.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone rejoins too fast during verification
|
||||
too-fast-reconnect:
|
||||
- <header>
|
||||
- <gold>Połączenie zostało nawiązane zbyt szybko, spróbuj ponownie.
|
||||
- <gray>Odczekaj kilka sekund przed ponowną próbą weryfikacji.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already verifying
|
||||
already-verifying:
|
||||
- <header>
|
||||
- <red>Twój adres IP jest obecnie weryfikowany.
|
||||
- <red>Odczekaj kilka sekund przed ponowną próbą weryfikacji.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already queued for verification
|
||||
already-queued:
|
||||
- <header>
|
||||
- <red>Twój adres IP znajduje się obecnie w kolejce do weryfikacji.
|
||||
- <red>Odczekaj kilka minut przed ponowną próbą weryfikacji.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is temporarily blacklisted
|
||||
blacklisted:
|
||||
- <header>
|
||||
- <red>Obecnie nie możesz wejść na serwer.
|
||||
- <red>Odczekaj kilka minut, aby móc ponownie dołączyć do serwera.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with an invalid username
|
||||
invalid-username:
|
||||
- <header>
|
||||
- <red>Twoja nazwa użytkownika zawiera nieprawidłowe znaki.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with a blacklisted version
|
||||
blacklisted-protocol:
|
||||
- <header>
|
||||
- <red>Używasz wersji, która nie jest dozwolona na naszym serwerze.
|
||||
- <gold>Potrzebujesz pomocy przy logowaniu? <gray><support-url>
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone verifies successfully
|
||||
success:
|
||||
- <header>
|
||||
- <green>Pomyślnie przeszedłeś weryfikację.
|
||||
- <white>Możesz teraz grać na serwerze po ponownym połączeniu.
|
||||
# Disconnect message that is shown when someone fails verification
|
||||
failed:
|
||||
- <header>
|
||||
- <red>Weryfikacja nie powiodła się.
|
||||
- <gray>Odczekaj kilka sekund przed ponowną próbą weryfikacji.
|
||||
- <gold>Potrzebujesz pomocy przy logowaniu? <gray><support-url>
|
||||
- <footer>
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
layout:
|
||||
# Layout for Sonar's actionbar verbose
|
||||
normal: <prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s
|
||||
<white><logins-per-second> <dark_aqua>╺ <gray>Verifying <white><verifying> <dark_aqua>|
|
||||
<green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: <prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s
|
||||
<white><logins-per-second> <dark_aqua>╺ <gray>Queue <white><queued> <dark_aqua>╺
|
||||
<gray>Verifying <white><verifying> <dark_aqua>╺ <gray>Duration <white><attack-duration>
|
||||
<dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>
|
||||
# Animation for the action bar
|
||||
# Alternatives:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
# - ⬈, ⬊, ⬋, ⬉
|
||||
animation:
|
||||
- ◜
|
||||
- ◝
|
||||
- ◞
|
||||
- ◟
|
||||
|
||||
# Translations for Sonar's attack notification messages
|
||||
notifications:
|
||||
# Layout for Sonar's attack notification title
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# Layout for Sonar's attack notification subtitle
|
||||
subtitle: <gray>Wykryto atak botów
|
||||
# Layout for Sonar's attack notification in chat
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Wykryto atak botów.
|
||||
- ' <dark_aqua>▪ <gray>Logowania na sekundę: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Weryfikacja graczy: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Gracze w kolejce: <white><queued>'
|
||||
- ''
|
||||
- <gray>Wyświetl więcej informacji za pomocą <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar stats'><hover:show_text:'(Kliknij, aby uruchomić)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -191,12 +191,12 @@ verification:
|
||||
|
||||
# CAPTCHA'yı girmeleri gerektiğinde oyuncuya gösterilen mesajlar
|
||||
captcha:
|
||||
# Captcha'nın cevabını girmeleri gerektiğinde oyuncuya gösterilen mesaj
|
||||
# CAPTCHA'nın cevabını girmeleri gerektiğinde oyuncuya gösterilen mesaj
|
||||
enter-code: '<prefix><green>Haritada gösterilen kodu lütfen sohbete girin.'
|
||||
# Message that is shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>Yanlış kodu girdiniz. Lütfen tekrar deneyin.'
|
||||
|
||||
# Captcha henüz hazırlanmamışken birisi katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
# CAPTCHA henüz hazırlanmamışken birisi katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <yellow>Anti-bot verileriniz henüz hazır değil.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# Potrzebujesz pomocy lub masz pytania? https://jonesdev.xyz/discord
|
||||
# Source code: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Bot attack notifications can also be sent to your Discord server using webhooks
|
||||
webhook:
|
||||
# URL of the Discord webhook (Set this to '' to disable webhooks)
|
||||
url: ''
|
||||
# Username of the Discord webhook sender
|
||||
username: Sonar
|
||||
# URL to the avatar of the Discord webhook sender (Set this to '' to disable)
|
||||
avatar-url: ''
|
||||
# Content of the Discord webhook message (Set this to '' to disable)
|
||||
# You can use this to e.g. ping staff members using <@userId>
|
||||
# If you want to ping roles, you will need to use <@&roleId>
|
||||
content: ''
|
||||
# Embed Discord webhook message that is sent when an attack has stopped
|
||||
embed:
|
||||
# Small footer message of the Discord webhook embed
|
||||
footer:
|
||||
# Content of the footer message of the Discord webhook embed
|
||||
text: © Jones Development and Sonar Contributors
|
||||
# URL of the footer message icon of the Discord webhook embed
|
||||
icon-url: ''
|
||||
# Title of the Discord webhook embed
|
||||
title: ':white_check_mark: Atak został złagodzony'
|
||||
# Clickable URL of the title of the Discord webhook embed
|
||||
title-url: ''
|
||||
# Description (content) of the Discord webhook embed
|
||||
description:
|
||||
- Atak na serwer został złagodzony.
|
||||
- ''
|
||||
- 'Początek ataku: <t:<start-timestamp>:T>'
|
||||
- 'Koniec ataku: <t:<end-timestamp>:T>'
|
||||
- 'Czas trwania ataku: <attack-duration>'
|
||||
- ''
|
||||
- 'Szczytowe użycie procesora przez proces podczas ataku: <peak-cpu>%'
|
||||
- 'Szczytowe użycie pamięci procesu podczas ataku: <peak-memory>'
|
||||
- 'Szczytowa liczba botów na sekundę podczas ataku: <peak-bps>'
|
||||
- ''
|
||||
- 'Adresy IP na czarnej liście podczas ataku: <total-blacklisted>'
|
||||
- 'Nieudane weryfikacje podczas ataku: <total-failed>'
|
||||
- 'Pomyślne weryfikacje podczas ataku: <total-success>'
|
||||
# RGB colors of the Discord webhook embed
|
||||
# Color picker: https://www.rapidtables.com/web/color/RGB_Color.html
|
||||
color:
|
||||
red: 0
|
||||
green: 255
|
||||
blue: 0
|
||||
@@ -34,13 +34,10 @@ import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
public static SonarVelocity INSTANCE;
|
||||
|
||||
public SonarVelocity(final @NotNull SonarVelocityPlugin plugin) {
|
||||
super(plugin, SonarPlatform.VELOCITY, plugin.getDataDirectory().toFile(),
|
||||
new VelocityLibraryManager<>(plugin, plugin.getLogger(),
|
||||
plugin.getDataDirectory(), plugin.getServer().getPluginManager()));
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +51,13 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
return getPlugin().getServer().getPlayer(uniqueId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public @NotNull Audience sender(final @NotNull Object object) {
|
||||
// We don't have to do this on Velocity since all command senders are also audiences.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
@@ -90,6 +94,10 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
() -> getConfig().getVerification().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("captcha",
|
||||
() -> getConfig().getVerification().getMap().getTiming().getDisplayName()));
|
||||
metrics.addCustomChart(new SimplePie("language",
|
||||
() -> getConfig().getLanguage().getName()));
|
||||
metrics.addCustomChart(new SimplePie("database_type",
|
||||
() -> getConfig().getDatabase().getType().getDisplayName()));
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand());
|
||||
|
||||
+20
-22
@@ -20,39 +20,37 @@ package xyz.jonesdev.sonar.velocity.fallback;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandlerAdapter;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
|
||||
public final class FallbackVelocityChannelHandler extends FallbackChannelHandlerAdapter {
|
||||
|
||||
public FallbackVelocityChannelHandler(final @NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
final class FallbackVelocityInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
// TODO: put this into a separate handler
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
// Intercept any handshake packet by the client
|
||||
if (listenForPackets) {
|
||||
if (msg instanceof HandshakePacket handshake) {
|
||||
handleHandshake(handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
else if (msg instanceof ServerLoginPacket serverLogin) {
|
||||
// Make sure to use the potentially modified, original IP
|
||||
final MinecraftConnection minecraftConnection = (MinecraftConnection) channel.pipeline().get(HANDLER);
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
|
||||
handleLogin(ctx, serverLogin, serverLogin.getUsername(), socketAddress,
|
||||
MINECRAFT_ENCODER, MINECRAFT_DECODER, READ_TIMEOUT, HANDLER);
|
||||
return;
|
||||
if (msg instanceof HandshakePacket handshake) {
|
||||
handleHandshake(ctx.channel(), handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
|
||||
// We don't care about server pings; remove the handler
|
||||
if (handshake.getNextStatus() == 1) {
|
||||
ctx.channel().pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
else if (msg instanceof ServerLoginPacket serverLogin) {
|
||||
// Make sure to use the potentially modified, original IP
|
||||
final MinecraftConnection minecraftConnection = (MinecraftConnection) ctx.channel().pipeline().get(HANDLER);
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.channel().pipeline().remove(this);
|
||||
// Let Sonar process the login packet
|
||||
handleLogin(ctx.channel(), ctx, () -> ctx.fireChannelRead(msg), serverLogin.getUsername(), socketAddress);
|
||||
return;
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
+6
-6
@@ -24,12 +24,12 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.injection.FallbackInjectedChannelInitializer;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_HANDLER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_HANDLER;
|
||||
|
||||
@UtilityClass
|
||||
public class FallbackVelocityInjector {
|
||||
@@ -54,12 +54,12 @@ public class FallbackVelocityInjector {
|
||||
|
||||
// Make sure to store the original channel initializer
|
||||
final ChannelInitializer<Channel> originalInitializer = connectionManager.serverChannelInitializer.get();
|
||||
final ChannelInitializer<Channel> fallbackInitializer = new FallbackInjectedChannelInitializer(
|
||||
originalInitializer, pipeline -> pipeline.addAfter(MINECRAFT_DECODER, FALLBACK_HANDLER,
|
||||
new FallbackVelocityChannelHandler(pipeline.channel())));
|
||||
final ChannelInitializer<Channel> injectedInitializer = new FallbackInjectedChannelInitializer(
|
||||
originalInitializer, pipeline -> pipeline.addAfter(MINECRAFT_DECODER, FALLBACK_PACKET_HANDLER,
|
||||
new FallbackVelocityInboundHandler()));
|
||||
|
||||
// Replace the original channel initializer
|
||||
SERVER_CHANNEL_INITIALIZER_FIELD.set(connectionManager.getServerChannelInitializer(), fallbackInitializer);
|
||||
SERVER_CHANNEL_INITIALIZER_FIELD.set(connectionManager.getServerChannelInitializer(), injectedInitializer);
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user