Compare commits

...
22 Commits
Author SHA1 Message Date
Jones 47cb4d0840 Release 2.1.5 (#377) 2024-09-09 14:53:56 +02:00
dependabot[bot] 38ecb9ac06 Bump io.netty:netty-all from 4.1.112.Final to 4.1.113.Final (#376)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.112.Final to 4.1.113.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.112.Final...netty-4.1.113.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 14:31:43 +02:00
Jones 6657a3d674 Merge all custom logger wrappers into one 2024-09-08 18:32:35 +02:00
Jones 823ee21cea Clear up a small misconception in the code 2024-09-08 18:16:51 +02:00
Jones 4d206af0ae Don't allow players to send empty packets 2024-09-08 00:47:25 +02:00
Jones 2ff7b7efaa Fix gravity check false positive on 1.7 2024-09-07 23:19:10 +02:00
Jones a5efc1c796 Recode Y position and gravity calculation 2024-09-07 20:00:52 +02:00
Jones 85d5ce77b1 Implement SetPlayerRotationPacket packet order check 2024-09-07 19:34:46 +02:00
Jones 6c4d4b79d6 Implement PaddleBoatPacket packet order check 2024-09-07 19:34:19 +02:00
Jones 364b1c5b31 Optimize image RGB buffer writing 2024-09-07 14:48:55 +02:00
Jones 4edf04ede5 Randomize some placeholder variables during login 2024-09-07 12:53:24 +02:00
Jones 01da1a70ea Remove useless PlayerInfoPacket 2024-09-07 12:00:53 +02:00
Jones 9bb7c7cd60 Only log expired database entries if there are any 2024-09-07 11:20:12 +02:00
Jones aaa7588dcd Don't load the database driver twice 2024-09-07 11:19:06 +02:00
Jones 8d378d56ef Use regular JdbcConnectionSource, not pooled 2024-09-07 11:17:49 +02:00
Jones 476b1704b8 VarInt encoding optimizations (https://github.com/PaperMC/Velocity/commit/f034c0277d60e9f0a80e484832aec831cbc298f7) 2024-09-06 23:16:41 +02:00
Jones 26c31ceeaf VarInt reading optimizations (https://github.com/PaperMC/Velocity/pull/1418) 2024-09-06 23:07:26 +02:00
Jones 40f65ef5eb Rename 1.16(.2) codecs for more clarity 2024-09-06 22:54:50 +02:00
Jones 7736ccff86 Fix multiple block types being incorrect on 1.16 2024-09-06 22:53:24 +02:00
Jones 437a547ec3 [ci skip] Fix translation errors in copyright notice 2024-09-06 22:42:03 +02:00
Jones 4ec65218da [ci skip] Remove unused configuration entries 2024-09-06 22:31:15 +02:00
Jones de45e9f808 Mention TransferTool for Geyser in the config 2024-09-04 17:38:02 +02:00
53 changed files with 307 additions and 501 deletions

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev.sonar
version=2.1.4
version=2.1.5
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
netty = "4.1.112.Final"
netty = "4.1.113.Final"
adventure = "4.17.0"
adventure-platform = "4.3.4"
bstats = "3.0.3"
@@ -21,6 +21,7 @@ import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.simpleyaml.configuration.MemorySection;
import org.simpleyaml.configuration.file.YamlFile;
import xyz.jonesdev.sonar.api.Sonar;
import java.io.File;
import java.io.InputStream;
@@ -31,8 +32,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static xyz.jonesdev.sonar.api.config.SonarConfiguration.LOGGER;
@Getter
public final class SimpleYamlConfig {
private final File file;
@@ -90,7 +89,7 @@ public final class SimpleYamlConfig {
public int getInt(final String path) {
if (!yaml.contains(path)) {
LOGGER.warn("Could not find {} in {}.", path, file.getName());
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
return 0;
}
return yaml.getInt(path);
@@ -98,7 +97,7 @@ public final class SimpleYamlConfig {
public boolean getBoolean(final String path) {
if (!yaml.contains(path)) {
LOGGER.warn("Could not find {} in {}.", path, file.getName());
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
return false;
}
return yaml.getBoolean(path);
@@ -106,7 +105,7 @@ public final class SimpleYamlConfig {
public @NotNull String getString(final String path) {
if (!yaml.contains(path)) {
LOGGER.warn("Could not find {} in {}.", path, file.getName());
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
return "";
}
final Object object = yaml.get(path);
@@ -118,7 +117,7 @@ public final class SimpleYamlConfig {
public List<String> getStringList(final String path) {
if (!yaml.contains(path)) {
LOGGER.warn("Could not find {} in {}.", path, file.getName());
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
return new ArrayList<>(0);
}
return yaml.getStringList(path);
@@ -126,7 +125,7 @@ public final class SimpleYamlConfig {
public List<Integer> getIntList(final String path) {
if (!yaml.contains(path)) {
LOGGER.warn("Could not find {} in {}.", path, file.getName());
Sonar.get().getLogger().warn("Could not find {} in {}.", path, file.getName());
return new ArrayList<>(0);
}
return yaml.getIntegerList(path);
@@ -30,7 +30,6 @@ import xyz.jonesdev.sonar.api.command.SonarCommand;
import xyz.jonesdev.sonar.api.database.ormlite.H2DatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MariaDbDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.database.ormlite.MysqlDatabaseTypeAdapter;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import xyz.jonesdev.sonar.api.webhook.DiscordWebhook;
import java.io.File;
@@ -52,24 +51,6 @@ public final class SonarConfiguration {
@Getter
private Language language;
static final LoggerWrapper LOGGER = new LoggerWrapper() {
@Override
public void info(final String message, final Object... args) {
Sonar.get().getLogger().info("[config] " + message, args);
}
@Override
public void warn(final String message, final Object... args) {
Sonar.get().getLogger().warn("[config] " + message, args);
}
@Override
public void error(final String message, final Object... args) {
Sonar.get().getLogger().error("[config] " + message, args);
}
};
public SonarConfiguration(final @NotNull File pluginFolder) {
this.pluginFolder = pluginFolder;
this.messagesConfig = new SimpleYamlConfig(new File(pluginFolder, "messages.yml"));
@@ -85,14 +66,14 @@ public final class SonarConfiguration {
final URL defaultLanguageFile = Sonar.class.getResource("/assets/language.properties");
// Make sure the file actually exists before trying to copy it
if (defaultLanguageFile == null) {
LOGGER.error("Cannot check for custom language (is the file missing?)");
Sonar.get().getLogger().error("Cannot check for custom language (is the file missing?)");
return DEFAULT_FALLBACK_LANGUAGE;
}
// Copy the file to the plugin data directory
try (final InputStream inputStream = defaultLanguageFile.openStream()) {
Files.copy(inputStream, languageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException exception) {
LOGGER.error("Error copying file: {}", exception);
Sonar.get().getLogger().error("Error copying file: {}", exception);
return DEFAULT_FALLBACK_LANGUAGE;
}
}
@@ -106,13 +87,13 @@ public final class SonarConfiguration {
// Try parsing the property as a language
return Language.fromCode(property);
} catch (Throwable throwable) {
LOGGER.error("Could not find requested language: {}", throwable);
LOGGER.error("You can view a full list of valid language codes here:");
LOGGER.error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
LOGGER.error("If a translation does not exist yet, Sonar will use English (en).");
Sonar.get().getLogger().error("Could not find requested language: {}", throwable);
Sonar.get().getLogger().error("You can view a full list of valid language codes here:");
Sonar.get().getLogger().error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
Sonar.get().getLogger().error("If a translation does not exist yet, Sonar will use English (en).");
}
} catch (IOException exception) {
LOGGER.error("Error reading language file: {}", exception);
Sonar.get().getLogger().error("Error reading language file: {}", exception);
}
return DEFAULT_FALLBACK_LANGUAGE;
}
@@ -131,13 +112,13 @@ public final class SonarConfiguration {
final String property = System.getProperty("user.language", "en");
language = Language.fromCode(property);
// Make sure the user knows that we're using the system language for translations
LOGGER.info("Using system language ({}) for translations.", language);
Sonar.get().getLogger().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.", language);
Sonar.get().getLogger().warn("Could not use system language for translations.");
Sonar.get().getLogger().warn("Using default language ({}) for translations.", language);
}
} else {
LOGGER.info("Using custom language ({}) for translations.", language);
Sonar.get().getLogger().info("Using custom language ({}) for translations.", language);
}
// Load all configurations
@@ -177,12 +158,12 @@ public final class SonarConfiguration {
verification.timing = Verification.Timing.valueOf(generalConfig.getString("verification.timing"));
// Warn the user if the verification timing is set to NEVER
if (verification.timing == Verification.Timing.NEVER) {
LOGGER.warn(" ");
LOGGER.warn("You have set the verification timing to 'NEVER'.");
LOGGER.warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
LOGGER.warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
LOGGER.warn("Please only edit this option if you really know what you are doing.");
LOGGER.warn(" ");
Sonar.get().getLogger().warn(" ");
Sonar.get().getLogger().warn("You have set the verification timing to 'NEVER'.");
Sonar.get().getLogger().warn("Sonar will NOT perform the bot verification at all, therefore making it useless.");
Sonar.get().getLogger().warn("It is highly suggested to set this option to either 'DURING_ATTACK' or 'ALWAYS'.");
Sonar.get().getLogger().warn("Please only edit this option if you really know what you are doing.");
Sonar.get().getLogger().warn(" ");
}
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled");
@@ -303,7 +284,7 @@ public final class SonarConfiguration {
final String resourceName = url + "/" + language.getCode() + ".yml";
URL result = Sonar.class.getResource("/assets/" + resourceName);
if (result == null) {
LOGGER.warn("Could not find " + resourceName + "! Using en.yml!");
Sonar.get().getLogger().warn("Could not find " + resourceName + "! Using en.yml!");
result = Objects.requireNonNull(Sonar.class.getResource("/assets/" + url + "/en.yml"));
}
return result;
@@ -489,7 +470,7 @@ public final class SonarConfiguration {
private final DatabaseType databaseType;
private final Library databaseDriver;
@Setter
private boolean downloaded;
private boolean loaded;
}
private Type type;
@@ -20,7 +20,7 @@ package xyz.jonesdev.sonar.api.database.controller;
import com.alessiodp.libby.LibraryManager;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
@@ -32,7 +32,6 @@ import org.jetbrains.annotations.Unmodifiable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.database.model.VerifiedPlayer;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import java.io.File;
import java.sql.SQLException;
@@ -47,24 +46,6 @@ import java.util.concurrent.Executors;
public final class VerifiedPlayerController {
private static final ExecutorService DB_UPDATE_SERVICE = Executors.newSingleThreadExecutor();
private static final LoggerWrapper LOGGER = new LoggerWrapper() {
@Override
public void info(final String message, final Object... args) {
Sonar.get().getLogger().info("[database] " + message, args);
}
@Override
public void warn(final String message, final Object... args) {
Sonar.get().getLogger().warn("[database] " + message, args);
}
@Override
public void error(final String message, final Object... args) {
Sonar.get().getLogger().error("[database] " + message, args);
}
};
private final Map<String, Collection<UUID>> cache = new ConcurrentHashMap<>(128);
private @Nullable ConnectionSource connectionSource;
private Dao<VerifiedPlayer, Integer> dao;
@@ -77,20 +58,18 @@ public final class VerifiedPlayerController {
cachedDatabaseType = database.getType();
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
LOGGER.warn("Configure a database to save verified players.");
Sonar.get().getLogger().warn("Configure a database to save verified players.");
return;
}
// Make sure to only download the driver once
if (!cachedDatabaseType.isDownloaded()) {
LOGGER.info("Downloading {} driver version {}",
// Make sure to only load the driver once per database type
if (!cachedDatabaseType.isLoaded()) {
Sonar.get().getLogger().info("Loading {} driver version {}",
cachedDatabaseType.getDatabaseType().getDatabaseName(),
cachedDatabaseType.getDatabaseDriver().getVersion());
// Download and load the driver for the current database
libraryManager.loadLibrary(cachedDatabaseType.getDatabaseDriver());
cachedDatabaseType.setDownloaded(true);
cachedDatabaseType.setLoaded(true);
}
cachedDatabaseType.getDatabaseType().loadDriver();
try {
final String jdbcURL;
@@ -109,7 +88,7 @@ public final class VerifiedPlayerController {
Sonar.get().getConfig().getGeneralConfig().getString("database.name"));
}
connectionSource = new JdbcPooledConnectionSource(jdbcURL,
connectionSource = new JdbcConnectionSource(jdbcURL,
Sonar.get().getConfig().getGeneralConfig().getString("database.username"),
Sonar.get().getConfig().getGeneralConfig().getString("database.password"),
cachedDatabaseType.getDatabaseType());
@@ -137,11 +116,11 @@ public final class VerifiedPlayerController {
// Add all entries from the database to the cache
dao.queryForAll().forEach(this::_add);
} catch (SQLException exception) {
LOGGER.error("Error initializing database: {}", exception);
Sonar.get().getLogger().error("Error initializing database: {}", exception);
}
});
} catch (SQLException exception) {
LOGGER.error("Error setting up database: {}", exception);
Sonar.get().getLogger().error("Error setting up database: {}", exception);
}
}
@@ -154,7 +133,7 @@ public final class VerifiedPlayerController {
try {
connectionSource.close();
} catch (Exception exception) {
LOGGER.error("Error closing database: {}", exception);
Sonar.get().getLogger().error("Error closing database: {}", exception);
}
}
}
@@ -171,11 +150,11 @@ public final class VerifiedPlayerController {
.lt("timestamp", new Timestamp(timestamp))
.query();
if (oldEntries != null) {
if (oldEntries != null && !oldEntries.isEmpty()) {
for (final VerifiedPlayer player : oldEntries) {
dao.delete(player);
}
LOGGER.info("Removed {} database entries older than {} days.",
Sonar.get().getLogger().info("Removed {} database entries older than {} days.",
oldEntries.size(), maximumAge);
}
}
@@ -212,7 +191,7 @@ public final class VerifiedPlayerController {
}
}
} catch (SQLException exception) {
LOGGER.error("Error trying to remove entry: {}", exception);
Sonar.get().getLogger().error("Error trying to remove entry: {}", exception);
}
});
}
@@ -250,7 +229,7 @@ public final class VerifiedPlayerController {
try {
dao.createIfNotExists(player);
} catch (SQLException exception) {
LOGGER.error("Error trying to add entry: {}", exception);
Sonar.get().getLogger().error("Error trying to add entry: {}", exception);
}
});
}
@@ -306,7 +285,7 @@ public final class VerifiedPlayerController {
try {
dao.deleteBuilder().delete();
} catch (SQLException exception) {
LOGGER.error("Error trying to clear entries: {}", exception);
Sonar.get().getLogger().error("Error trying to clear entries: {}", exception);
}
}
}
@@ -27,7 +27,6 @@ import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import java.net.InetAddress;
import java.util.concurrent.ConcurrentHashMap;
@@ -54,24 +53,6 @@ public final class Fallback {
@Setter
private Ratelimiter<InetAddress> ratelimiter;
private final LoggerWrapper logger = new LoggerWrapper() {
@Override
public void info(final String message, final Object... args) {
Sonar.get().getLogger().info("[fallback] " + message, args);
}
@Override
public void warn(final String message, final Object... args) {
Sonar.get().getLogger().warn("[fallback] " + message, args);
}
@Override
public void error(final String message, final Object... args) {
Sonar.get().getLogger().error("[fallback] " + message, args);
}
};
public boolean shouldVerifyNewPlayers() {
return shouldPerform(Sonar.get().getConfig().getVerification().getTiming());
}
@@ -34,7 +34,7 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTailExceptionsHandler;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarIntLengthEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
@@ -94,7 +94,7 @@ public final class FallbackUserWrapper implements FallbackUser {
if (Sonar.get().getConfig().getVerification().isLogConnections()
&& (Sonar.get().getAttackTracker().getCurrentAttack() == null
|| Sonar.get().getConfig().getVerification().isLogDuringAttack())) {
Sonar.get().getFallback().getLogger().info(
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
.replace("<username>", username)
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
@@ -116,7 +116,7 @@ public final class FallbackUserWrapper implements FallbackUser {
// Replace normal encoder to allow custom packets
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
@@ -147,7 +147,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|| Sonar.get().getConfig().getVerification().isLogDuringAttack();
if (shouldLog) {
Sonar.get().getFallback().getLogger().info(
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.failed")
.replace("<ip>", Sonar.get().getConfig().formatAddress(getInetAddress()))
.replace("<protocol>", String.valueOf(getProtocolVersion().getProtocol()))
@@ -179,7 +179,7 @@ public final class FallbackUserWrapper implements FallbackUser {
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
if (shouldLog) {
Sonar.get().getFallback().getLogger().info(
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
.replace("<ip>", Sonar.get().getConfig().formatAddress(getInetAddress()))
.replace("<protocol>", String.valueOf(getProtocolVersion().getProtocol())));
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.netty;
import io.netty.util.ByteProcessor;
import lombok.Getter;
// 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
}
}
@@ -20,57 +20,102 @@ 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.DecoderException;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
import java.util.List;
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
// 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) {
protected void decode(final @NotNull ChannelHandlerContext ctx,
final @NotNull ByteBuf byteBuf,
final @NotNull List<Object> out) throws Exception {
if (!ctx.channel().isActive()) {
in.clear();
byteBuf.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();
}
// Skip any runs of 0x00 we might find
final int packetStart = byteBuf.forEachByte(FIND_NON_NUL);
if (packetStart == -1) {
return;
}
byteBuf.readerIndex(packetStart);
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();
// Try to read the length of the packet
byteBuf.markReaderIndex();
final int preIndex = byteBuf.readerIndex();
final int length = readRawVarInt21(byteBuf);
if (preIndex == byteBuf.readerIndex()) {
return;
}
if (length <= 0) {
throw DEBUG ? new DecoderException("Bad VarInt length: " + length) : QuietDecoderException.INSTANCE;
}
if (readVarInt < 0) {
in.clear();
throw QuietDecoderException.INSTANCE;
} 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 QuietDecoderException.INSTANCE;
if (byteBuf.readableBytes() < length) {
byteBuf.resetReaderIndex();
} else {
out.add(byteBuf.readRetainedSlice(length));
}
}
private static int readRawVarInt21(final @NotNull ByteBuf byteBuf) {
if (byteBuf.readableBytes() < 4) {
return readRawVarIntSmallBuffer(byteBuf);
}
// take the last three bytes and check if any of them have the high bit set
final int wholeOrMore = byteBuf.getIntLE(byteBuf.readerIndex());
final int atStop = ~wholeOrMore & 0x808080;
if (atStop == 0) {
throw DEBUG ? new DecoderException("VarInt too big") : QuietDecoderException.INSTANCE;
}
final int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
byteBuf.skipBytes(bitsToKeep >> 3);
// https://github.com/netty/netty/pull/14050#issuecomment-2107750734
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));
// https://github.com/netty/netty/pull/14050#discussion_r1597896639
preservedBytes = (preservedBytes & 0x007F007F) | ((preservedBytes & 0x00007F00) >> 1);
preservedBytes = (preservedBytes & 0x00003FFF) | ((preservedBytes & 0x3FFF0000) >> 2);
return preservedBytes;
}
private static int readRawVarIntSmallBuffer(final @NotNull ByteBuf byteBuf) {
if (!byteBuf.isReadable()) {
return 0;
}
byteBuf.markReaderIndex();
byte tmp = byteBuf.readByte();
if (tmp >= 0) {
return tmp;
}
int result = tmp & 0x7F;
if (!byteBuf.isReadable()) {
byteBuf.resetReaderIndex();
return 0;
}
if ((tmp = byteBuf.readByte()) >= 0) {
return result | tmp << 7;
}
result |= (tmp & 0x7F) << 7;
if (!byteBuf.isReadable()) {
byteBuf.resetReaderIndex();
return 0;
}
if ((tmp = byteBuf.readByte()) >= 0) {
return result | tmp << 14;
}
return result | (tmp & 0x7F) << 14;
}
}
@@ -20,30 +20,30 @@ 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 io.netty.handler.codec.MessageToMessageEncoder;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.varIntBytes;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
// 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();
public final class FallbackVarIntLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
public static final FallbackVarIntLengthEncoder INSTANCE = new FallbackVarIntLengthEncoder();
@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);
}
final @NotNull ByteBuf byteBuf,
final @NotNull List<Object> out) throws Exception {
final int length = byteBuf.readableBytes();
final int varIntLength = varIntBytes(length);
@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);
final ByteBuf lenBuf = ctx.alloc().buffer(varIntLength);
writeVarInt(lenBuf, length);
out.add(lenBuf);
out.add(byteBuf.retain());
}
}
@@ -217,22 +217,6 @@ public enum FallbackPacketRegistry {
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
map(0x01, MINECRAFT_1_19_4, true));
clientbound.register(PlayerInfoPacket.class, PlayerInfoPacket::new,
map(0x38, MINECRAFT_1_7_2, true),
map(0x2D, MINECRAFT_1_9, true),
map(0x2E, MINECRAFT_1_12_1, true),
map(0x30, MINECRAFT_1_13, true),
map(0x33, MINECRAFT_1_14, true),
map(0x34, MINECRAFT_1_15, true),
map(0x33, MINECRAFT_1_16, true),
map(0x32, MINECRAFT_1_16_2, true),
map(0x36, MINECRAFT_1_17, true),
map(0x34, MINECRAFT_1_19, true),
map(0x37, MINECRAFT_1_19_1, true),
map(0x36, MINECRAFT_1_19_3, true),
map(0x3A, MINECRAFT_1_19_4, true),
map(0x3C, MINECRAFT_1_20_2, true),
map(0x3E, MINECRAFT_1_20_5, true));
clientbound.register(UpdateSectionBlocksPacket.class, UpdateSectionBlocksPacket::new,
map(0x22, MINECRAFT_1_7_2, true),
map(0x10, MINECRAFT_1_9, true),
@@ -34,12 +34,12 @@ import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPac
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import xyz.jonesdev.sonar.common.util.ComponentHolder;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
@UtilityClass
public class FallbackPreparer {
private final SecureRandom RANDOM = new SecureRandom();
private final Random RANDOM = new Random();
// LoginSuccess
public FallbackPacket loginSuccess;
// Abilities
@@ -66,9 +66,6 @@ public class FallbackPreparer {
public FallbackPacket joinGame;
// Update Section Blocks
public FallbackPacket updateSectionBlocks;
// Player Info
public final FallbackPacket PLAYER_INFO = new FallbackPacketSnapshot(new PlayerInfoPacket(
"", new UUID(1L, 1L), 2));
// Default Spawn Position
public FallbackPacket defaultSpawnPosition;
// Spawn Position
@@ -96,7 +93,7 @@ public class FallbackPreparer {
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 DEFAULT_Y_COLLIDE_POSITION = 155 + RANDOM.nextInt(101); // 255 is the maximum Y position
public final int IN_AIR_Y_POSITION = 1337;
// CAPTCHA position
@@ -106,7 +103,6 @@ public class FallbackPreparer {
// Platform
public BlockType blockType;
public int maxMovementTick, dynamicSpawnYPosition;
public double[] preparedCachedYMotions;
public double maxFallDistance;
// XP packets
@@ -119,25 +115,24 @@ public class FallbackPreparer {
FallbackPacketRegistry.values();
// Prepare LoginSuccess packet
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(new UUID(1L, 1L),
loginSuccess = new FallbackPacketSnapshot(new LoginSuccessPacket(UUID.randomUUID(),
Sonar.get().getConfig().getGeneralConfig().getString("verification.cached-username")));
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(),
0, false, 0,
RANDOM.nextLong(), false, 0,
true, false, false,
new String[]{"minecraft:overworld"}, "minecraft:overworld"));
// Prepare cached motions for the gravity check
maxFallDistance = 0;
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
preparedCachedYMotions = new double[maxMovementTick + 8];
maxFallDistance = 1;
for (int i = 0; i < preparedCachedYMotions.length; i++) {
final double gravity = -((Math.pow(0.98, i) - 1) * 3.92);
preparedCachedYMotions[i] = gravity;
maxFallDistance += gravity;
double motionY = -0.08 * 0.98f;
for (int i = 0; i < maxMovementTick; i++) {
motionY = (motionY - 0.08) * 0.98f;
maxFallDistance += Math.abs(motionY);
}
// Set the dynamic block and collide Y position based on the maximum fall distance
@@ -52,8 +52,8 @@ public enum BlockType {
case MINECRAFT_1_15:
case MINECRAFT_1_15_1:
case MINECRAFT_1_15_2:
case MINECRAFT_1_16:
return 5116;
case MINECRAFT_1_16:
case MINECRAFT_1_16_1:
return 5132;
case MINECRAFT_1_16_2:
@@ -102,8 +102,8 @@ public enum BlockType {
case MINECRAFT_1_15:
case MINECRAFT_1_15_1:
case MINECRAFT_1_15_2:
case MINECRAFT_1_16:
return 7016;
case MINECRAFT_1_16:
case MINECRAFT_1_16_1:
return 7552;
case MINECRAFT_1_16_2:
@@ -54,15 +54,14 @@ public class CaptchaPreparer {
// Prepare everything asynchronously
PREPARATION_SERVICE.execute(() -> {
for (preparedAmount = 0; preparedAmount < precomputeAmount;) {
// Generate CAPTCHA answer
// Generate CAPTCHA
final char[] answer = new char[3 + RANDOM.nextInt(2)];
for (int j = 0; j < answer.length; j++) {
answer[j] = alphabet[RANDOM.nextInt(alphabet.length)];
}
final BufferedImage image = Sonar.get().getFallback().getCaptchaGenerator().createImage(answer);
// Convert and cache converted Minecraft map bytes
final int[] buffer = MapColorPalette.getBufferFromImage(image);
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), buffer);
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), MapColorPalette.imageToBuffer(image));
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
@@ -32,14 +32,13 @@ public final class MapCaptchaInfo {
private final FallbackPacket[] legacy;
private final FallbackPacket modern;
public MapCaptchaInfo(final @NotNull String answer, final int @NotNull [] buffer) {
public MapCaptchaInfo(final @NotNull String answer, final byte @NotNull [] buffer) {
this.answer = answer;
// Prepare 1.7 map data using a grid
final int[][] grid = new int[128][128];
final byte[][] grid = new byte[128][128];
for (int i = 0; i < buffer.length; i++) {
final int buf = buffer[i];
grid[i & Byte.MAX_VALUE][i >> 7] = buf;
grid[i & Byte.MAX_VALUE][i >> 7] = buffer[i];
}
this.legacy = new FallbackPacket[grid.length];
for (int i = 0; i < grid.length; i++) {
@@ -21,6 +21,7 @@ import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
@UtilityClass
public class MapColorPalette {
@@ -172,25 +173,32 @@ public class MapColorPalette {
-12910336,
};
public int[] getBufferFromImage(final @NotNull BufferedImage bufferedImage) {
final int[] buffer = new int[128 * 128];
bufferedImage.getRGB(0, 0,
bufferedImage.getWidth(), bufferedImage.getHeight(),
buffer, 0, bufferedImage.getWidth());
for (int i = 0; i < buffer.length; i++) {
buffer[i] = mapColorToMinecraft(buffer[i]);
}
public byte[] imageToBuffer(final @NotNull BufferedImage bufferedImage) {
final byte[] buffer = new byte[128 * 128];
writeRGBBuffer(bufferedImage, buffer);
return buffer;
}
private byte mapColorToMinecraft(final int argb) {
private void writeRGBBuffer(final @NotNull BufferedImage bufferedImage, final byte @NotNull [] rgbArray) {
final Raster raster = bufferedImage.getRaster();
final Object data = new byte[raster.getNumBands()];
int yoff = 0, off;
for (int y = 0; y < raster.getHeight(); y++, yoff += raster.getWidth()) {
off = yoff;
for (int x = 0; x < raster.getWidth(); x++) {
final int rgb = bufferedImage.getColorModel().getRGB(raster.getDataElements(x, y, data));
rgbArray[off++] = rgbToNearestMinecraft(rgb);
}
}
}
private byte rgbToNearestMinecraft(final int rgb) {
int minDistance = Integer.MAX_VALUE;
byte closestIndex = 0;
for (int i = 0; i < COLOR_MAP.length; i++) {
final byte index = (byte) (i < 128 ? i : -129 + (i - 127));
final int distance = calculateColorDistance(argb, COLOR_MAP[i]);
final int distance = calculateColorDistance(rgb, COLOR_MAP[i]);
if (distance < minDistance) {
minDistance = distance;
closestIndex = index;
@@ -30,38 +30,32 @@ import java.util.Objects;
@UtilityClass
public final class DimensionRegistry {
public final CompoundBinaryTag CODEC_1_16;
public final CompoundBinaryTag CODEC_1_16_2;
public final CompoundBinaryTag CODEC_1_18_2;
public final CompoundBinaryTag CODEC_1_19;
public final CompoundBinaryTag CODEC_1_19_1;
public final CompoundBinaryTag CODEC_1_19_4;
public final CompoundBinaryTag CODEC_1_20;
public final CompoundBinaryTag CODEC_1_21;
public final DimensionInfo DEFAULT_DIMENSION_1_16;
public final DimensionInfo DEFAULT_DIMENSION_1_18_2;
public final @NotNull CompoundBinaryTag CODEC_1_16;
public final @NotNull CompoundBinaryTag CODEC_1_18_2;
public final @NotNull CompoundBinaryTag CODEC_1_19;
public final @NotNull CompoundBinaryTag CODEC_1_19_1;
public final @NotNull CompoundBinaryTag CODEC_1_19_4;
public final @NotNull CompoundBinaryTag CODEC_1_20;
public final @NotNull CompoundBinaryTag CODEC_1_21;
public final @NotNull CompoundBinaryTag OLD_CODEC;
static {
CODEC_1_16 = getCodec("codec_1_16.nbt");
CODEC_1_16_2 = getCodec("codec_1_16_2.nbt");
CODEC_1_18_2 = getCodec("codec_1_18_2.nbt");
CODEC_1_19 = getCodec("codec_1_19.nbt");
CODEC_1_19_1 = getCodec("codec_1_19_1.nbt");
CODEC_1_19_4 = getCodec("codec_1_19_4.nbt");
CODEC_1_20 = getCodec("codec_1_20.nbt");
CODEC_1_21 = getCodec("codec_1_21.nbt");
OLD_CODEC = getCodec("codec_old.nbt");
DEFAULT_DIMENSION_1_16 = getDimension(CODEC_1_16);
DEFAULT_DIMENSION_1_16 = getDimension(CODEC_1_16_2);
DEFAULT_DIMENSION_1_18_2 = getDimension(CODEC_1_18_2);
}
private static @NotNull DimensionInfo getDimension(final @NotNull CompoundBinaryTag tag) {
final ListBinaryTag dimensions = tag.getCompound("minecraft:dimension_type").getList("value");
final BinaryTag elementTag = ((CompoundBinaryTag) dimensions.get(0)).get("element");
return new DimensionInfo("minecraft:overworld", 0, (CompoundBinaryTag) elementTag);
}
private @NotNull CompoundBinaryTag getCodec(final @NotNull String fileName) {
try (final InputStream inputStream = Sonar.class.getResourceAsStream("/assets/codecs/" + fileName)) {
return BinaryTagIO.reader().read(Objects.requireNonNull(inputStream), BinaryTagIO.Compression.GZIP);
@@ -70,4 +64,10 @@ public final class DimensionRegistry {
throw new IllegalStateException(throwable);
}
}
private static @NotNull DimensionInfo getDimension(final @NotNull CompoundBinaryTag tag) {
final ListBinaryTag dimensions = tag.getCompound("minecraft:dimension_type").getList("value");
final BinaryTag elementTag = ((CompoundBinaryTag) dimensions.get(0)).get("element");
return new DimensionInfo("minecraft:overworld", 0, (CompoundBinaryTag) elementTag);
}
}
@@ -104,7 +104,7 @@ public final class JoinGamePacket implements FallbackPacket {
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
writeCompoundTag(byteBuf, OLD_CODEC);
writeCompoundTag(byteBuf, CODEC_1_16);
writeString(byteBuf, DEFAULT_DIMENSION_1_16.getIdentifier());
writeString(byteBuf, levelName);
byteBuf.writeLong(partialHashedSeed);
@@ -122,7 +122,7 @@ public final class JoinGamePacket implements FallbackPacket {
byteBuf.writeByte(gamemode);
byteBuf.writeByte(-1); // previous gamemode
writeStringArray(byteBuf, levelNames);
writeCompoundTag(byteBuf, CODEC_1_16);
writeCompoundTag(byteBuf, CODEC_1_16_2);
writeCompoundTag(byteBuf, DEFAULT_DIMENSION_1_16.getTag());
writeString(byteBuf, levelName);
byteBuf.writeLong(partialHashedSeed);
@@ -31,7 +31,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
@NoArgsConstructor
@AllArgsConstructor
public final class MapDataPacket implements FallbackPacket {
private int[] buffer;
private byte[] buffer;
private int x, y;
@Override
@@ -43,7 +43,7 @@ public final class MapDataPacket implements FallbackPacket {
byteBuf.writeByte(0); // scaling
byteBuf.writeByte(x);
byteBuf.writeByte(y);
writeArray(byteBuf, buffer);
byteBuf.writeBytes(buffer);
} else {
byteBuf.writeByte(0); // scaling
@@ -68,13 +68,7 @@ public final class MapDataPacket implements FallbackPacket {
byteBuf.writeByte(y);
writeVarInt(byteBuf, buffer.length);
writeArray(byteBuf, buffer);
}
}
private static void writeArray(final ByteBuf byteBuf, final int @NotNull [] array) {
for (final int i : array) {
byteBuf.writeByte(i);
byteBuf.writeBytes(buffer);
}
}
@@ -1,98 +0,0 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import java.util.EnumSet;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public final class PlayerInfoPacket implements FallbackPacket {
private String username;
private UUID uuid;
private int gamemode;
@Override
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
writeString(byteBuf, username);
byteBuf.writeBoolean(true); // online
byteBuf.writeShort(0);
return;
}
// https://wiki.vg/Protocol#player-info:player-actions
if (protocolVersion.compareTo(MINECRAFT_1_19_3) >= 0) {
final EnumSet<Action> actions = EnumSet.noneOf(Action.class);
actions.add(Action.ADD_PLAYER);
actions.add(Action.UPDATE_LISTED);
actions.add(Action.UPDATE_GAMEMODE);
writeEnumSet(byteBuf, actions, Action.class);
writeVarInt(byteBuf, 1); // size
writeUUID(byteBuf, uuid);
writeString(byteBuf, username);
writeVarInt(byteBuf, 0); // properties
byteBuf.writeBoolean(true); // update listed
writeVarInt(byteBuf, gamemode);
return;
}
writeVarInt(byteBuf, 0); // ADD_PLAYER
writeVarInt(byteBuf, 1);
writeUUID(byteBuf, uuid);
writeString(byteBuf, username);
writeVarInt(byteBuf, 0);
writeVarInt(byteBuf, gamemode);
writeVarInt(byteBuf, 60);
byteBuf.writeBoolean(false);
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
byteBuf.writeBoolean(false);
}
}
@Override
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
public enum Action {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAMEMODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME
}
}
@@ -52,10 +52,6 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
if (user.getProtocolVersion().compareTo(MINECRAFT_1_19_3) >= 0) {
user.delayedWrite(defaultSpawnPosition);
}
// Write the PlayerInfo packet to the buffer
if (user.getProtocolVersion().compareTo(MINECRAFT_1_16_4) >= 0) {
user.delayedWrite(PLAYER_INFO);
}
// 1.20.3+ introduced game events
// Make sure the client knows that we're sending chunks next
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_3) >= 0) {
@@ -166,7 +162,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
// Log/debug position if enabled in the configuration
if (Sonar.get().getConfig().getVerification().isDebugXYZPositions()) {
Sonar.get().getFallback().getLogger().info("{}: {}/{}/{}, ly={}/dy={}, {}",
Sonar.get().getLogger().info("{}: {}/{}/{}, ly={}/dy={}, {}",
username, x, y, z, lastY, deltaY, isOnGround);
}
@@ -83,7 +83,7 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
}
Sonar.get().getFallback().getLogger().info(
Sonar.get().getLogger().info(
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.successful")
.replace("<username>", username)
.replace("<time-taken>", user.getLoginTimer().toString()));
@@ -148,12 +148,18 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
// Don't ask me why; Microsoft is doing some *fascinating* things with Bedrock...
if (user.isGeyser()) {
handleRotation();
} else {
checkState(rotationPackets >= inputPackets,
"illegal packet order; r/i " + rotationPackets + "/" + inputPackets);
}
// 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) {
paddlePackets++;
} else {
checkState(paddlePackets >= inputPackets,
"illegal packet order; i/p " + inputPackets + "/" + paddlePackets);
}
inputPackets++;
}
@@ -22,7 +22,6 @@ import com.google.gson.JsonParser;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
@@ -37,24 +36,6 @@ import java.util.concurrent.Executors;
public class UpdateChecker {
private final ExecutorService ASYNC_EXECUTOR = Executors.newSingleThreadExecutor();
private final LoggerWrapper LOGGER = new LoggerWrapper() {
@Override
public void info(final String message, final Object... args) {
Sonar.get().getLogger().info("[update-checker] " + message, args);
}
@Override
public void warn(final String message, final Object... args) {
Sonar.get().getLogger().warn("[update-checker] " + message, args);
}
@Override
public void error(final String message, final Object... args) {
Sonar.get().getLogger().error("[update-checker] " + message, args);
}
};
public void checkForUpdates() {
ASYNC_EXECUTOR.execute(() -> {
try {
@@ -66,17 +47,17 @@ public class UpdateChecker {
final int convertedCurrentVersion = convertVersion(Sonar.get().getVersion().getVersion());
if (convertedCurrentVersion < convertedLatestVersion) {
LOGGER.warn("A new version of Sonar is available: {}", latestStableRelease);
LOGGER.warn("Please make sure to update to the latest version to ensure stability and security:");
LOGGER.warn("https://github.com/jonesdevelopment/sonar/releases/tag/{}", latestStableRelease);
Sonar.get().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/tag/{}", latestStableRelease);
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().getGitBranch().equals("main")) {
LOGGER.warn("You are currently using an unreleased version of Sonar!");
LOGGER.warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
Sonar.get().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
} else {
LOGGER.info("You are currently using the latest stable release of Sonar!");
Sonar.get().getLogger().info("You are currently using the latest stable release of Sonar!");
}
} catch (Throwable throwable) {
LOGGER.warn("Unable to retrieve version information: {}", throwable);
Sonar.get().getLogger().warn("Unable to retrieve version information: {}", throwable);
}
});
}
@@ -63,6 +63,18 @@ public class ProtocolUtil {
public static final boolean DEBUG = Boolean.getBoolean("sonar.debug-traces");
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
public static final String BRAND_CHANNEL = "minecraft:brand";
private static final int[] VAR_INT_LENGTHS = new int[65];
static {
for (int i = 0; i <= 32; ++i) {
VAR_INT_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
}
VAR_INT_LENGTHS[32] = 1;
}
public static int varIntBytes(final int value) {
return VAR_INT_LENGTHS[Integer.numberOfLeadingZeros(value)];
}
public static void checkNettyVersion() {
final Version version = Version.identify().getOrDefault("netty-all", Version.identify().get("netty-common"));
@@ -83,25 +95,29 @@ public class ProtocolUtil {
}
}
public static int readVarInt(final ByteBuf byteBuf) {
int read = readVarIntSafely(byteBuf);
if (read == Integer.MIN_VALUE) {
throw DEBUG ? new DecoderException("Bad VarInt") : QuietDecoderException.INSTANCE;
public static int readVarInt(final @NotNull ByteBuf byteBuf) {
final int readable = byteBuf.readableBytes();
if (readable == 0) {
throw DEBUG ? new DecoderException("Empty buffer") : QuietDecoderException.INSTANCE;
}
return read;
}
public static int readVarIntSafely(final @NotNull ByteBuf byteBuf) {
int i = 0;
int maxRead = Math.min(5, byteBuf.readableBytes());
for (int j = 0; j < maxRead; j++) {
int k = byteBuf.readByte();
// We can read at least one byte, and this should be a common case
int k = byteBuf.readByte();
if ((k & 0x80) != 128) {
return k;
}
// In case decoding one byte was not enough, use a loop to decode up to the next 4 bytes
final int maxRead = Math.min(5, readable);
int i = k & 0x7F;
for (int j = 1; j < maxRead; j++) {
k = byteBuf.readByte();
i |= (k & 0x7F) << j * 7;
if ((k & 0x80) != 128) {
return i;
}
}
return Integer.MIN_VALUE;
throw DEBUG ? new DecoderException("Bad VarInt") : QuietDecoderException.INSTANCE;
}
public static void writeVarInt(final ByteBuf byteBuf, final int value) {
@@ -139,19 +155,6 @@ 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.
@@ -10,7 +10,8 @@
#
# Potřebujete pomoc nebo máte otázky? https://jonesdev.xyz/discord
# Zdrojový kód: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development a Sonar Contributors, licencováno pod GPL-3.0
# Dokumentace: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, licencováno pod GPL-3.0
#
# Obecné nastavení pro Sonar
@@ -139,9 +140,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex pro validaci "client locale" při verifikaci
valid-locale-regex: ^[a-zA-Z_]+$
# Maximální odezva hráče aby byl odpojen
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
max-ping: 10000
# Maximální počet login packetů, který musí hřáč poslat aby byl vyhozen
max-login-packets: 256
@@ -155,6 +153,9 @@ verification:
# 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áči správné přenesení.
# Pokud váš server umožňuje hráčům Bedrocku připojit se přes Geyser, nezapomeňte nastavit.
# TransferTool pro Geyser, aby byli hráči Bedrocku přesměrováni na správný server.
# https://github.com/onebeastchris/TransferTool
enabled: false
# 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.
@@ -10,7 +10,8 @@
#
# Brauchst du Hilfe? https://jonesdev.xyz/discord
# Source code: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Dokumentation: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, lizenziert unter GPL-3.0
#
# Generelle Einstellungen für Sonar
@@ -136,9 +137,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex zur Validierung der Client-Spracheinstellung während der Überprüfung
valid-locale-regex: ^[a-zA-Z_]+$
# Ping, den ein Spieler haben muss, um ein Timeout zu erleiden
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
max-ping: 10000
# Maximale Anzahl an Login-Paketen, die der Spieler senden muss, um gekickt zu werden
max-login-packets: 256
@@ -152,6 +150,9 @@ verification:
# 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.
# Wenn Ihr Server es erlaubt, dass Bedrock-Spieler über Geyser beitreten können, stellen Sie sicher, dass Sie das
# TransferTool für Geyser einrichten, damit Bedrock-Spieler auf den richtigen Server umgeleitet werden.
# https://github.com/onebeastchris/TransferTool
enabled: false
# Auf welchen Server soll Sonar den Spieler übertragen, wenn die Verifizierung bestanden wurde?
# Bitte geben Sie die Server-IP ein, die von Spielern verwendet wird, um sich zu verbinden.
@@ -10,6 +10,7 @@
#
# Need help or have questions? https://jonesdev.xyz/discord
# Source code: https://github.com/jonesdevelopment/sonar
# Documentation: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
@@ -62,7 +63,7 @@ database:
# Every new login request will be queued to avoid spam join attacks
# The queue is updated every 1 second (20 ticks)
queue:
# Maximum number of concurrent queue polls per 1 second
# Number of concurrent queue polls per 1 second
max-polls: 30
# Every new player that joins for the first time will be sent to
@@ -82,7 +83,7 @@ verification:
enabled: 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
# The number of ticks the player has to fall to be allowed to hit the platform
max-movement-ticks: 8
# Checks if the player is colliding with blocks spawned below them
@@ -110,8 +111,8 @@ verification:
# - NEVER: New players will never receive a CAPTCHA (Recommended)
timing: NEVER
# Path to the background image Sonar uses for the CAPTCHA
# If the image isn't found, a noise will be used as the default background
# You can put the name of the file here if the file is in the plugin directory
# If the image isn't found, a noise will be used as the default background.
# You can put the name of the file here if the file is in Sonar's plugin directory
background: ''
# How many CAPTCHA answers should Sonar prepare?
# This task happens asynchronously in the background
@@ -139,10 +140,7 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex for validating client locale during verification
valid-locale-regex: ^[a-zA-Z_]+$
# Ping a player has to have to timeout
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
max-ping: 10000
# Maximum number of login packets the player has to send in order to be kicked
# The number of login packets the player has to send in order to be kicked
max-login-packets: 256
# If enabled, the player will be transferred back to the origin server
@@ -155,6 +153,9 @@ verification:
# 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.
# If your server allows Bedrock players to join via Geyser, make sure to set up
# TransferTool for Geyser, so Bedrock players are redirected to the correct server.
# https://github.com/onebeastchris/TransferTool
enabled: false
# Which server should Sonar transfer the player to when the verification is passed?
# Please enter the server IP used by players to normally connect to your server.
@@ -10,7 +10,8 @@
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
# Documentation: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, licence sous GPL-3.0
#
# Paramètre général de Sonar
@@ -138,9 +139,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex pour valider la locale client lors de la vérification
valid-locale-regex: ^[a-zA-Z_]+$
# Le ping qu'un joueur doit avoir pour dépasser le délai d'attente
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
max-ping: 10000
# Nombre maximal de paquets de connexion que le joueur doit envoyer pour être expulsé
max-login-packets: 256
@@ -154,6 +152,9 @@ verification:
# De plus, vous voudrez peut-être réduire la quantité de limitation de débit de connexion
# effectuée par Velocity ou d'autres proxys/plugins, car cela pourrait empêcher le
# joueur d'être transféré correctement.
# Si votre serveur permet aux joueurs de Bedrock de s'inscrire via Geyser, assurez-vous de configurer
# TransferTool pour Geyser, afin que les joueurs Bedrock soient redirigés vers le bon serveur.
# https://github.com/onebeastchris/TransferTool
enabled: false
# Quel serveur Sonar devrait-il utiliser pour transférer le joueur une fois la vérification réussie ?
# Veuillez entrer l'adresse IP du serveur que les joueurs utilisent normalement pour se connecter à votre serveur.
@@ -10,6 +10,7 @@
#
# დახმარება გჭირდება ან კითხვა გაქვს? https://jonesdev.xyz/discord
# საწყისი კოდი: https://github.com/jonesdevelopment/sonar
# Documentation: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
#
@@ -138,9 +139,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex-ი კლიენტის ადგილის დასადასტურებლად ვერიფიკაციის დროს
valid-locale-regex: ^[a-zA-Z_]+$
# პინგი რამდენიც უნდა ქონდეს მოთამაშეს იმისთვის, რომ დროებით დაიბლოკოს (გავარდეს)
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
max-ping: 10000
# მაქსიმალური რაოდენობა შესვლის (LOGIN) პაკეტების, რომელიც მოთამაშემ უნდა გააგზავნოს სანამ გავარდება სერვერიდან
max-login-packets: 256
@@ -153,6 +151,9 @@ verification:
# დამატებით, შეიძლება გინდოდეთ შეამციროთ შესვლის სიხშირის ლიმიტი
# რომელსაც ახორციელებს Velocity ან სხვა პროქსიები/პლაგინები, რადგან ეს შეიძლება ხელს უშლიდეს
# მოთამაშის სწორად გადაყვანას
# If your server allows Bedrock players to join via Geyser, make sure to set up
# TransferTool for Geyser, so Bedrock players are redirected to the correct server.
# https://github.com/onebeastchris/TransferTool
enabled: false
# რომელ სერვერზე უნდა გადაიყვანოს Sonar-მა მოთამაშე ვერიფიკაციის გავლის შემდეგ?
# გთხოვთ, შეიყვანოთ სერვერის IP მისამართი, რომლითაც მოთამაშეები ჩვეულებრივ უერთდებიან თქვენს სერვერს
@@ -10,7 +10,8 @@
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Documentatie: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, gelicentieerd onder GPL-3.0
#
# Algemene instellingen voor Sonar
@@ -138,9 +139,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex voor het valideren van de landinstelling van de client tijdens verificatie
valid-locale-regex: ^[a-zA-Z_]+$
# Ping moet een speler hebben om een time-out te krijgen
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
max-ping: 10000
# Maximaal aantal loginpakketten dat de speler moet verzenden om te worden geschopt
max-login-packets: 256
@@ -154,6 +152,9 @@ verification:
# Bovendien wilt u mogelijk de beperking van de inlegsnelheid verminderen
# uitgevoerd door Velocity van andere proxy's/plugins, omdat dit de
# speler wordt niet correct weergegeven.
# Als je server toestaat dat Bedrock spelers meedoen via Geyser, zorg er dan voor dat je
# TransferTool voor Geyser, zodat Bedrock spelers worden doorgestuurd naar de juiste server.
# https://github.com/onebeastchris/TransferTool
enabled: false
# Naar welke server moet Sonar de speler overbrengen wanneer de verificatie is geslaagd?
# Voer het server-IP in dat door spelers wordt gebruikt om normaal verbinding te maken met uw server.
@@ -10,7 +10,8 @@
#
# 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
# Dokumentacja: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, na licencji GPL-3.0
#
# Ustawienia ogólne Sonar
@@ -138,9 +139,6 @@ verification:
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
@@ -154,6 +152,9 @@ verification:
# 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.
# Jeśli twój serwer pozwala graczom Bedrock na dołączenie przez Geyser, upewnij się, że skonfigurowałeś
# TransferTool dla Geyser, aby gracze Bedrock byli przekierowywani na właściwy serwer.
# https://github.com/onebeastchris/TransferTool
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.
@@ -10,7 +10,8 @@
#
# Нужна помощь или есть вопросы? https://jonesdev.xyz/discord
# Исходный код: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Документация: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, под лицензией GPL-3.0
#
# Основные настройки
@@ -137,9 +138,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# Regex для проверки локализации клиента при верификации
valid-locale-regex: ^[a-zA-Z_]+$
# Пинг который должен быть у игрока чтобы не пройти проверку
# (Это значение представляет собой время в миллисекундах: 1 секунда = 1000 миллисекунд)
max-ping: 10000
# Максимальное количество пакетов для входа на сервер, которое игрок должен отправить, чтобы быть выгнанным
max-login-packets: 256
@@ -153,6 +151,9 @@ verification:
# Кроме того, вы можете пожелать снизить количество ограничений на скорость входа
# выполняемых Velocity или другими прокси/плагинами, так как это может помешать
# корректному перемещению игрока.
# Если ваш сервер позволяет игрокам Bedrock присоединяться через Geyser, убедитесь, что вы настроили
# TransferTool для Geyser, чтобы игроки Bedrock перенаправлялись на правильный сервер.
# https://github.com/onebeastchris/TransferTool
enabled: false
# На какой сервер должен Sonar переместить игрока после прохождения проверки?
# Введите IP-адрес сервера, который обычно используется игроками для подключения к вашему серверу.
@@ -10,7 +10,8 @@
#
# 需要帮助吗? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# 文件: https://docs.jonesdev.xyz/administration/configuring-sonar
# Copyright Jones Development and Sonar Contributors, 以 GPL-3.0 许可
#
# 未归类的设置
@@ -135,8 +136,6 @@ verification:
valid-name-regex: ^[a-zA-Z0-9_]+$
# 用于检查客户端语言(例如 zh_CN)的正则表达式
valid-locale-regex: ^[a-zA-Z_]+$
# 玩家必须在多少毫秒内回复Ping?
max-ping: 10000
# 玩家最多能发送多少个数据包用于登录?
max-login-packets: 256
@@ -148,6 +147,9 @@ verification:
# 为此您必须在服务器软件的配置文件中允许接受来自transfer的连接.
# 此外 您可能希望减少 Velocity 或其它服务器软件/插件的登录速率限制
# 因为这可能会阻止玩家快速地重新连接到服务器
# 如果你的服务器允许 Bedrock 玩家通过间歇泉加入, 请确保为间歇泉设置了
# 传输工具 TransferTool, 以便将 Bedrock 玩家重定向到正确的服务器.
# https://github.com/onebeastchris/TransferTool
enabled: false
# 当玩家通过验证后, Sonar 应该将玩家转移到哪个服务器
# 请输入玩家正常连接服务器时使用的服务器IP或域名
@@ -10,7 +10,7 @@
#
# Brauchst du Hilfe? https://jonesdev.xyz/discord
# Source code: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, lizenziert unter GPL-3.0
#
# Platzhalter für jedes '<prefix>' in dieser Konfigurationsdatei
@@ -10,7 +10,7 @@
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
# Copyright Jones Development and Sonar Contributors, licence sous GPL-3.0
#
# Espace réservé pour tout '<prefix>' dans ce fichier de configuration
@@ -10,7 +10,7 @@
#
# Butuh bantuan atau memiliki pertanyaan? https://jonesdev.xyz/discord
# Kode sumber: https://github.com/jonesdevelopment/sonar
# Hak cipta Jones Development dan Kontributor Sonar, dilisensikan di bawah GPL-3.0
# Copyright Jones Development and Sonar Contributors, dilisensikan di bawah GPL-3.0
#
# Placeholder untuk setiap '<prefix>' dalam file konfigurasi ini
@@ -10,7 +10,7 @@
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, gelicentieerd onder GPL-3.0
#
# Tijdelijke aanduiding voor elke '<prefix>' in dit configuratiebestand
@@ -10,7 +10,7 @@
#
# 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
# Copyright Jones Development and Sonar Contributors, na licencji GPL-3.0
#
# Placeholder for every '<prefix>' in this configuration file
@@ -10,7 +10,7 @@
#
# Нужна помощь или есть вопросы? https://jonesdev.xyz/discord
# Исходный код: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, под лицензией GPL-3.0
#
# Плейсхолдер для '<prefix>' в конфиге
@@ -10,7 +10,7 @@
#
# Yardıma mı ihtiyacınız var veya sorularınız mı var? https://jonesdev.xyz/discord
# Kaynak kodu: https://github.com/jonesdevelopment/sonar
# Jones Development ve Sonar Katkıda Bulunanlar tarafından GPL-3.0 lisansı altında lisanslanmıştır
# Copyright Jones Development and Sonar Contributors, GPL-3.0 altında lisanslanmıştır
#
# Bu yapılandırma dosyasındaki her '<prefix>' için yer tutucu
@@ -10,7 +10,7 @@
#
# 需要帮助吗? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, 以 GPL-3.0 许可
#
# <prefix> 占位符
@@ -10,7 +10,7 @@
#
# Potřebujete pomoc nebo máte otázky? https://jonesdev.xyz/discord
# Zdrojový kód: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development a Sonar Contributors, licencováno pod GPL-3.0
# Copyright Jones Development and Sonar Contributors, licencováno pod GPL-3.0
#
# Notifikace útoků můžou být poslány na discord server pomocí webhooků
@@ -10,7 +10,7 @@
#
# Brauchst du Hilfe? https://jonesdev.xyz/discord
# Source code: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, lizenziert unter GPL-3.0
#
# Bot-Angriffsbenachrichtigungen können auch über Webhooks an deinen Discord-Server gesendet werden
@@ -10,7 +10,7 @@
#
# Besoin d'aide ou d'autres questions ? https://jonesdev.xyz/discord
# Code source : https://github.com/jonesdevelopment/sonar
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
# Copyright Jones Development and Sonar Contributors, licence sous GPL-3.0
#
# Les notifications d'attaque bot peuvent être envoyé à discord via un webhook
@@ -10,7 +10,7 @@
#
# Butuh bantuan atau punya pertanyaan? https://jonesdev.xyz/discord
# Kode sumber: https://github.com/jonesdevelopment/sonar
# Hak cipta Jones Development dan Kontributor Sonar, dilisensikan di bawah GPL-3.0
# Copyright Jones Development and Sonar Contributors, dilisensikan di bawah GPL-3.0
#
# Notifikasi serangan bot juga dapat dikirim ke server Discord Anda menggunakan webhook
@@ -10,7 +10,7 @@
#
# Hulp nodig of vragen? https://jonesdev.xyz/discord
# Broncode: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, gelicentieerd onder GPL-3.0
#
# Bot aanval meldingen kunnen ook met behulp van webhooks naar uw Discord-server worden verzonden
@@ -10,7 +10,7 @@
#
# 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
# Copyright Jones Development and Sonar Contributors, na licencji GPL-3.0
#
# Bot attack notifications can also be sent to your Discord server using webhooks
@@ -10,7 +10,7 @@
#
# Нужна помощь или есть вопросы? https://jonesdev.xyz/discord
# Исходный код: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, под лицензией GPL-3.0
#
# Уведомления о бот-атаках могут быть отправлены на ваш сервер Discord с помощью вебхуков
@@ -10,7 +10,7 @@
#
# 需要帮助吗? https://jonesdev.xyz/discord
# 源代码: https://github.com/jonesdevelopment/sonar
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
# Copyright Jones Development and Sonar Contributors, 以 GPL-3.0 许可
#
# 当遭到机器人攻击时也可以将webhook消息发送到Discord服务器中.