Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87e768da0e | ||
|
|
daa773bff1 | ||
|
|
1e43835d0b | ||
|
|
efb8b3cd39 | ||
|
|
540899e92c | ||
|
|
ef6140d47f | ||
|
|
27ca5a8368 | ||
|
|
1a43242c24 | ||
|
|
65eeec6e17 | ||
|
|
d8e4dbd6cf | ||
|
|
32973e674f | ||
|
|
05fa6de119 | ||
|
|
cf0f4e0b96 | ||
|
|
0ae3c6c67b | ||
|
|
aae564d16c | ||
|
|
f6338ba844 | ||
|
|
d892645779 | ||
|
|
def40be5c0 | ||
|
|
d4bdf86fcb |
No files matched your search
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.1.2
|
||||
version=2.1.4
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -21,17 +21,17 @@ import net.kyori.adventure.audience.Audience;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.attack.AttackTracker;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.database.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEventManager;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.notification.ActionBarNotificationHandler;
|
||||
import xyz.jonesdev.sonar.api.notification.ChatNotificationHandler;
|
||||
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
import xyz.jonesdev.sonar.api.tracker.AttackTracker;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.UUID;
|
||||
@@ -41,7 +41,7 @@ public interface Sonar {
|
||||
|
||||
/**
|
||||
* Since we want to use Adventure on every server platform,
|
||||
* we have to use their platform module to support BungeeCord and Bukkit
|
||||
* we have to use their platform module to support BungeeCord and Bukkit.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@Nullable Audience audience(final @Nullable UUID uniqueId);
|
||||
@@ -49,14 +49,8 @@ public interface Sonar {
|
||||
@ApiStatus.Internal
|
||||
@NotNull Audience sender(final @NotNull Object object);
|
||||
|
||||
/**
|
||||
* @return The platform the plugin is being run on
|
||||
*/
|
||||
@NotNull SonarPlatform getPlatform();
|
||||
|
||||
/**
|
||||
* @return A small wrapper for the plugin logger so we can use the logger everywhere
|
||||
*/
|
||||
@NotNull LoggerWrapper getLogger();
|
||||
|
||||
@NotNull SonarConfiguration getConfig();
|
||||
@@ -65,29 +59,20 @@ public interface Sonar {
|
||||
|
||||
VerifiedPlayerController getVerifiedPlayerController();
|
||||
|
||||
@NotNull Verbose getVerboseHandler();
|
||||
@NotNull ActionBarNotificationHandler getActionBarNotificationHandler();
|
||||
|
||||
@NotNull Notification getNotificationHandler();
|
||||
@NotNull ChatNotificationHandler getChatNotificationHandler();
|
||||
|
||||
@NotNull SystemTimer getLaunchTimer();
|
||||
|
||||
@NotNull SonarStatistics getStatistics();
|
||||
|
||||
/**
|
||||
* Set a custom verbose handler
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setVerboseHandler(final @NotNull Verbose verboseHandler);
|
||||
void setActionBarNotificationHandler(final @NotNull ActionBarNotificationHandler notificationHandler);
|
||||
|
||||
/**
|
||||
* Set a custom notification handler
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setNotificationHandler(final @NotNull Notification notificationHandler);
|
||||
void setChatNotificationHandler(final @NotNull ChatNotificationHandler notificationHandler);
|
||||
|
||||
/**
|
||||
* Reloads the entire plugin
|
||||
*/
|
||||
void reload();
|
||||
|
||||
@NotNull
|
||||
@@ -97,19 +82,16 @@ public interface Sonar {
|
||||
|
||||
@NotNull
|
||||
default SonarEventManager getEventManager() {
|
||||
// We don't want anyone else to create a new instance.
|
||||
return SonarEventManager.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default AttackTracker getAttackTracker() {
|
||||
// We don't want anyone else to create a new instance.
|
||||
return AttackTracker.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default Fallback getFallback() {
|
||||
// We don't want anyone else to create a new instance.
|
||||
return Fallback.INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,21 +26,15 @@ import java.util.function.Function;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum SonarPlatform {
|
||||
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");
|
||||
BUKKIT("Bukkit", 19110, "packet_handler",
|
||||
pipeline -> pipeline.context("outbound_config") != null ? "outbound_config" : "encoder"),
|
||||
BUNGEE("BungeeCord", 19109, "inbound-boss",
|
||||
pipeline -> "packet-encoder"),
|
||||
VELOCITY("Velocity", 19107, "handler",
|
||||
pipeline -> "minecraft-encoder");
|
||||
|
||||
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;
|
||||
private final String connectionHandler;
|
||||
private final Function<ChannelPipeline, String> encoder;
|
||||
}
|
||||
@@ -19,21 +19,20 @@ package xyz.jonesdev.sonar.api;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import xyz.jonesdev.sonar.api.exception.AlreadyInitializedException;
|
||||
|
||||
@UtilityClass
|
||||
public class SonarSupplier {
|
||||
private Sonar sonar;
|
||||
private Sonar _sonar;
|
||||
|
||||
public void set(final @NotNull Sonar s) {
|
||||
if (sonar != null) {
|
||||
throw new AlreadyInitializedException();
|
||||
public void set(final @NotNull Sonar instance) {
|
||||
if (_sonar != null) {
|
||||
throw new AlreadyInitializedException("Sonar is already initialized");
|
||||
}
|
||||
sonar = Objects.requireNonNull(s);
|
||||
_sonar = instance;
|
||||
}
|
||||
|
||||
public @NotNull Sonar get() {
|
||||
return sonar;
|
||||
return _sonar;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ public final class SonarVersion {
|
||||
this.formatted = version + " (" + gitCommit + ")";
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/pull/1336/
|
||||
private static @NotNull Manifest getManifest() {
|
||||
final String classLocation = "/" + Sonar.class.getName().replace(".", "/") + ".class";
|
||||
|
||||
@@ -21,7 +21,6 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Predicate;
|
||||
@@ -34,23 +33,12 @@ public final class InvocationSource {
|
||||
private final Predicate<String> permissionFunction;
|
||||
|
||||
/**
|
||||
* @return True, if {@link InvocationSource#uuid} is not null
|
||||
* @apiNote This indicates a player as a player will always have a UUID
|
||||
* @return true if {@link InvocationSource#uuid} is not null
|
||||
*/
|
||||
public boolean isPlayer() {
|
||||
return uuid != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*
|
||||
* @apiNote We should probably use cached components...
|
||||
* (See {@link InvocationSource#sendMessage(Component)})
|
||||
*/
|
||||
public void sendMessage(final String legacy) {
|
||||
sendMessage(MiniMessage.miniMessage().deserialize(legacy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*/
|
||||
|
||||
@@ -25,34 +25,15 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface SubcommandInfo {
|
||||
|
||||
/**
|
||||
* @return Name of the subcommand
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* @return Aliases of the subcommand
|
||||
*/
|
||||
String[] aliases() default {};
|
||||
|
||||
/**
|
||||
* @return Arguments of the subcommand
|
||||
*/
|
||||
String[] arguments() default {};
|
||||
|
||||
/**
|
||||
* @return Whether the subcommands requires arguments
|
||||
*/
|
||||
boolean argumentsRequired() default true;
|
||||
|
||||
/**
|
||||
* @return Whether the subcommands can only be executed by a player
|
||||
*/
|
||||
boolean onlyPlayers() default false;
|
||||
|
||||
/**
|
||||
* @return Whether the subcommands can only be executed by console
|
||||
*/
|
||||
boolean onlyConsole() default false;
|
||||
}
|
||||
@@ -27,10 +27,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
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.ormlite.H2DatabaseTypeAdapter;
|
||||
import xyz.jonesdev.sonar.api.ormlite.MariaDbDatabaseTypeAdapter;
|
||||
import xyz.jonesdev.sonar.api.ormlite.MysqlDatabaseTypeAdapter;
|
||||
import xyz.jonesdev.sonar.api.webhook.DiscordWebhook;
|
||||
|
||||
import java.io.File;
|
||||
@@ -317,8 +317,8 @@ public final class SonarConfiguration {
|
||||
return output;
|
||||
}
|
||||
|
||||
public String formatAddress(final InetAddress inetAddress) {
|
||||
return logPlayerAddresses ? inetAddress.toString() : "/<ip address withheld>";
|
||||
public @NotNull String formatAddress(final @NotNull InetAddress inetAddress) {
|
||||
return logPlayerAddresses ? inetAddress.getHostAddress() : "<ip address withheld>";
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
+8
-9
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.controller;
|
||||
package xyz.jonesdev.sonar.api.database.controller;
|
||||
|
||||
import com.alessiodp.libby.LibraryManager;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
@@ -31,8 +31,8 @@ import org.jetbrains.annotations.Range;
|
||||
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 xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
@@ -74,10 +74,8 @@ public final class 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();
|
||||
|
||||
// Don't establish a database connection if the type is NONE
|
||||
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
|
||||
LOGGER.warn("Configure a database to save verified players.");
|
||||
return;
|
||||
@@ -92,7 +90,6 @@ public final class VerifiedPlayerController {
|
||||
libraryManager.loadLibrary(cachedDatabaseType.getDatabaseDriver());
|
||||
cachedDatabaseType.setDownloaded(true);
|
||||
}
|
||||
// Make sure to actually register the loader, so we can use it
|
||||
cachedDatabaseType.getDatabaseType().loadDriver();
|
||||
|
||||
try {
|
||||
@@ -120,10 +117,12 @@ public final class VerifiedPlayerController {
|
||||
// Create database table
|
||||
try {
|
||||
TableUtils.createTableIfNotExists(connectionSource, VerifiedPlayer.class);
|
||||
} catch (SQLException exception) {
|
||||
// Duplicate index
|
||||
// I know this isn't the best method of handling it,
|
||||
// but I don't know how else I could address this issue.
|
||||
} catch (SQLException ignored) {
|
||||
/*
|
||||
* This is caused by a duplicate index;
|
||||
* I know this isn't the best method of handling it,
|
||||
* but I don't know how else I could address this issue.
|
||||
*/
|
||||
}
|
||||
|
||||
dao = DaoManager.createDao(connectionSource, VerifiedPlayer.class);
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.model;
|
||||
package xyz.jonesdev.sonar.api.database.model;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
@@ -63,7 +63,7 @@ public final class VerifiedPlayer {
|
||||
public VerifiedPlayer(final @NotNull InetAddress inetAddress,
|
||||
final @NotNull UUID playerUuid,
|
||||
final long timestamp) {
|
||||
this(inetAddress.toString(), playerUuid, timestamp);
|
||||
this(inetAddress.getHostAddress(), playerUuid, timestamp);
|
||||
}
|
||||
|
||||
public VerifiedPlayer(final @NotNull String inetAddress,
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.ormlite;
|
||||
package xyz.jonesdev.sonar.api.database.ormlite;
|
||||
|
||||
import com.j256.ormlite.jdbc.db.H2DatabaseType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.ormlite;
|
||||
package xyz.jonesdev.sonar.api.database.ormlite;
|
||||
|
||||
import com.j256.ormlite.jdbc.db.MariaDbDatabaseType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.ormlite;
|
||||
package xyz.jonesdev.sonar.api.database.ormlite;
|
||||
|
||||
import com.j256.ormlite.jdbc.db.MysqlDatabaseType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -35,12 +35,9 @@ public final class SonarEventManager {
|
||||
private static final Collection<SonarEventListener> EVENT_LISTENERS = new Vector<>(0);
|
||||
private static final ExecutorService EVENT_SERVICE = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Publishes the given event to all listeners
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public void publish(final @NotNull SonarEvent event) {
|
||||
// Don't post a task if there are no listeners
|
||||
// Don't post an event if there are no listeners
|
||||
if (EVENT_LISTENERS.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -61,11 +58,13 @@ public final class SonarEventManager {
|
||||
* Registers one (or more) event listeners
|
||||
*
|
||||
* @param listeners One (or more) listeners to register
|
||||
* @see #unregisterListener(SonarEventListener...) Unregister an event listener
|
||||
* @see #unregisterListener(SonarEventListener[]) Unregister an event listener
|
||||
*/
|
||||
@SuppressWarnings("unused") // External API usage
|
||||
public synchronized void registerListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.addAll(Arrays.asList(listeners));
|
||||
public void registerListener(final @NotNull SonarEventListener... listeners) {
|
||||
synchronized (EVENT_LISTENERS) {
|
||||
EVENT_LISTENERS.addAll(Arrays.asList(listeners));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,10 +72,12 @@ public final class SonarEventManager {
|
||||
*
|
||||
* @param listeners One (or more) listeners to unregister
|
||||
* @apiNote This does not have any effect if the given listeners are not registered
|
||||
* @see #registerListener(SonarEventListener...) Register an event listener
|
||||
* @see #registerListener(SonarEventListener[]) Register an event listener
|
||||
*/
|
||||
@SuppressWarnings("unused") // External API usage
|
||||
public synchronized void unregisterListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.removeAll(Arrays.asList(listeners));
|
||||
public void unregisterListener(final @NotNull SonarEventListener... listeners) {
|
||||
synchronized (EVENT_LISTENERS) {
|
||||
EVENT_LISTENERS.removeAll(Arrays.asList(listeners));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ package xyz.jonesdev.sonar.api.event.impl;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.attack.AttackTracker;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.tracker.AttackTracker;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
|
||||
+11
-2
@@ -15,7 +15,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api;
|
||||
package xyz.jonesdev.sonar.api.exception;
|
||||
|
||||
final class AlreadyInitializedException extends RuntimeException {
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class AlreadyInitializedException extends RuntimeException {
|
||||
public AlreadyInitializedException(final @NotNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -15,7 +15,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api;
|
||||
package xyz.jonesdev.sonar.api.exception;
|
||||
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
|
||||
public final class ReflectiveOperationException extends RuntimeException {
|
||||
public ReflectiveOperationException(final Throwable throwable) {
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
|
||||
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
|
||||
import java.net.InetAddress;
|
||||
@@ -43,14 +44,15 @@ public final class Fallback {
|
||||
private final ConcurrentMap<InetAddress, Byte> connected = new ConcurrentHashMap<>(128);
|
||||
// Cache of all blacklisted IP addresses to ensure each entry can expire after the given time
|
||||
@Setter
|
||||
private Cache<InetAddress, Byte> blacklist;
|
||||
private Cache<String, Integer> blacklist;
|
||||
@Setter
|
||||
private long blacklistTime;
|
||||
@Setter
|
||||
private CaptchaGenerator captchaGenerator;
|
||||
|
||||
private final @NotNull FallbackQueue queue = new FallbackQueue();
|
||||
private final @NotNull FallbackRatelimiter ratelimiter = new FallbackRatelimiter();
|
||||
@Setter
|
||||
private Ratelimiter<InetAddress> ratelimiter;
|
||||
|
||||
private final LoggerWrapper logger = new LoggerWrapper() {
|
||||
|
||||
|
||||
@@ -1,65 +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.api.fallback;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Setter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public final class FallbackRatelimiter {
|
||||
private long timeout;
|
||||
private Cache<InetAddress, Long> attemptCache;
|
||||
@Getter
|
||||
private Cache<InetAddress, Integer> failCountCache;
|
||||
|
||||
/**
|
||||
* Attempts to rate-limit the client
|
||||
*
|
||||
* @param inetAddress IP address of the player
|
||||
* @return Whether the player is allowed to verify
|
||||
*/
|
||||
public boolean attempt(final @NotNull InetAddress inetAddress) {
|
||||
final long expectedTimestamp = System.currentTimeMillis() + timeout;
|
||||
// Check if the time since the last join has not exceeded the timeout
|
||||
// The idea for this check was Taken from Velocity
|
||||
final long last = attemptCache.get(inetAddress, result -> expectedTimestamp);
|
||||
return expectedTimestamp == last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player is failing verifications too often
|
||||
* Increments the number of times this user has failed the verification
|
||||
*
|
||||
* @param inetAddress IP address of the player
|
||||
* @param count Count of previously failed verifications
|
||||
*/
|
||||
public void incrementFails(final @NotNull InetAddress inetAddress, final int count) {
|
||||
// Make sure to remove the old values from the cache
|
||||
if (count > 0) {
|
||||
failCountCache.invalidate(inetAddress);
|
||||
}
|
||||
failCountCache.put(inetAddress, count + 1);
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -15,12 +15,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.command.subcommand.argument;
|
||||
package xyz.jonesdev.sonar.api.fallback.ratelimit;
|
||||
|
||||
public @interface Argument {
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @return Returns the name of the argument
|
||||
*/
|
||||
String value();
|
||||
@FunctionalInterface
|
||||
public interface Ratelimiter<T> {
|
||||
boolean attempt(final @NotNull T t);
|
||||
}
|
||||
@@ -33,7 +33,6 @@ public interface LoggerWrapper {
|
||||
|
||||
void error(final String message, final Object... args);
|
||||
|
||||
// Taken from
|
||||
// https://github.com/j256/ormlite-core/blob/master/src/main/java/com/j256/ormlite/logger/Logger.java
|
||||
String ARG_STRING = "{}";
|
||||
int ARG_STRING_LENGTH = ARG_STRING.length();
|
||||
|
||||
+5
-5
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.verbose;
|
||||
package xyz.jonesdev.sonar.api.notification;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
@@ -23,21 +23,21 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.attack.AttackTracker;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.tracker.AttackTracker;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
|
||||
import static xyz.jonesdev.sonar.api.timer.SystemTimer.DATE_FORMATTER;
|
||||
|
||||
@Getter
|
||||
public final class Verbose extends Observable {
|
||||
public final class ActionBarNotificationHandler extends NotificationHandler {
|
||||
private int animationIndex;
|
||||
|
||||
@Override
|
||||
public void observe() {
|
||||
public void handleNotification() {
|
||||
// Don't prepare component if there are no subscribers
|
||||
if (subscribers.isEmpty()) {
|
||||
return;
|
||||
+5
-5
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.verbose;
|
||||
package xyz.jonesdev.sonar.api.notification;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
@@ -29,13 +29,13 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
|
||||
|
||||
@Getter
|
||||
public final class Notification extends Observable {
|
||||
public final class ChatNotificationHandler extends NotificationHandler {
|
||||
|
||||
@Override
|
||||
public void observe() {
|
||||
public void handleNotification() {
|
||||
// Don't prepare component if there are no subscribers
|
||||
if (subscribers.isEmpty()) {
|
||||
return;
|
||||
@@ -74,7 +74,7 @@ public final class Notification extends Observable {
|
||||
}
|
||||
|
||||
// Send the title and chat messages to all online players
|
||||
for (final UUID subscriber : Sonar.get().getNotificationHandler().getSubscribers()) {
|
||||
for (final UUID subscriber : Sonar.get().getChatNotificationHandler().getSubscribers()) {
|
||||
final Audience audience = Sonar.get().audience(subscriber);
|
||||
if (audience == null) continue;
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.verbose;
|
||||
package xyz.jonesdev.sonar.api.notification;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -25,10 +25,10 @@ import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
@Getter
|
||||
public abstract class Observable {
|
||||
public abstract class NotificationHandler {
|
||||
protected final @NotNull Collection<UUID> subscribers = new Vector<>(0);
|
||||
|
||||
public abstract void observe();
|
||||
public abstract void handleNotification();
|
||||
|
||||
/**
|
||||
* @param uuid UUID of the player
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.jvm;
|
||||
package xyz.jonesdev.sonar.api.profiler;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import lombok.experimental.UtilityClass;
|
||||
@@ -24,7 +24,7 @@ import java.lang.management.ManagementFactory;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
@UtilityClass
|
||||
public class JVMProcessInformation {
|
||||
public class SimpleProcessProfiler {
|
||||
private final Runtime RUNTIME = Runtime.getRuntime();
|
||||
private final OperatingSystemMXBean MX = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
|
||||
private final char[] MEMORY_UNITS = {'K', 'M', 'G', 'T', 'P', 'E'};
|
||||
+4
-5
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.attack;
|
||||
package xyz.jonesdev.sonar.api.tracker;
|
||||
|
||||
import lombok.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -27,12 +27,13 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class AttackTracker {
|
||||
public static final AttackTracker INSTANCE = new AttackTracker();
|
||||
|
||||
private @Nullable AttackStatistics currentAttack;
|
||||
private int attackThreshold;
|
||||
|
||||
@@ -68,7 +69,7 @@ public final class AttackTracker {
|
||||
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
currentAttack.failedVerifications = Sonar.get().getStatistics().getTotalFailedVerifications();
|
||||
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
|
||||
Sonar.get().getNotificationHandler().observe();
|
||||
Sonar.get().getChatNotificationHandler().handleNotification();
|
||||
} else {
|
||||
// Reset attack timer if we're still under attack
|
||||
currentAttack.timer.reset();
|
||||
@@ -143,11 +144,9 @@ public final class AttackTracker {
|
||||
|
||||
// Reset the attack status
|
||||
currentAttack = null;
|
||||
// Reset threshold
|
||||
attackThreshold = 0;
|
||||
}
|
||||
} else {
|
||||
// Reset threshold
|
||||
attackThreshold = 0;
|
||||
}
|
||||
}
|
||||
@@ -120,8 +120,8 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
// Make sure to properly stop the metrics
|
||||
if (metrics != null) {
|
||||
// Make sure to properly shutdown bStats metrics
|
||||
metrics.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, S
|
||||
sender::hasPermission);
|
||||
// Pass the invocation source and command arguments to our command handler
|
||||
handle(invocationSource, args);
|
||||
return true; // Valid
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+22
-4
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* 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.bukkit.fallback;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
@@ -7,6 +24,7 @@ import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.HashSet;
|
||||
@@ -16,7 +34,7 @@ public final class ChannelInactiveListener extends ChannelInboundHandlerAdapter
|
||||
private final Set<ChannelInboundHandler> handlers = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
public void channelInactive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
final ProxiedChannelHandlerContext proxy = new ProxiedChannelHandlerContext(ctx);
|
||||
for (final ChannelInboundHandler handler : handlers) {
|
||||
try {
|
||||
@@ -28,9 +46,9 @@ public final class ChannelInactiveListener extends ChannelInboundHandlerAdapter
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
public void add(ChannelHandler handler) {
|
||||
if (handler instanceof ChannelInboundHandler) {
|
||||
handlers.add((ChannelInboundHandler) handler);
|
||||
public void add(final @NotNull ChannelHandler channelHandler) {
|
||||
if (channelHandler instanceof ChannelInboundHandler) {
|
||||
handlers.add((ChannelInboundHandler) channelHandler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -20,7 +20,7 @@ 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 io.netty.handler.codec.DecoderException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
@@ -28,14 +28,15 @@ 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 xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
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.DEBUG;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
|
||||
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
@@ -44,7 +45,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
|
||||
|
||||
channelRemovalListener = (pipeline, name, handler) -> {
|
||||
final var inactiveListener = (ChannelInactiveListener) pipeline.get(FALLBACK_INACTIVE_LISTENER);
|
||||
final ChannelInactiveListener inactiveListener = pipeline.get(ChannelInactiveListener.class);
|
||||
|
||||
if (inactiveListener != null) {
|
||||
inactiveListener.add(handler);
|
||||
@@ -66,8 +67,11 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
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
|
||||
/*
|
||||
* Reset the reader index, so we can read the content;
|
||||
* We also have to make sure to store the original reader index.
|
||||
* We do this to 'spoof' the packet state - we want to read it without the server knowing.
|
||||
*/
|
||||
final int originalReaderIndex = byteBuf.readerIndex();
|
||||
byteBuf.readerIndex(0);
|
||||
|
||||
@@ -82,7 +86,6 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -91,27 +94,27 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
// 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");
|
||||
throw DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
// 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");
|
||||
throw DEBUG ? new DecoderException("Could not read packet to end") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
// Useful resources:
|
||||
// https://wiki.vg/Protocol#Handshaking
|
||||
// https://wiki.vg/Protocol#Login
|
||||
/*
|
||||
* 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);
|
||||
ctx.pipeline().remove(this);
|
||||
break;
|
||||
case LOGIN:
|
||||
case TRANSFER:
|
||||
@@ -121,23 +124,22 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
|
||||
break;
|
||||
default:
|
||||
throw new CorruptedFrameException("Unknown handshake intent " + handshake.getIntent());
|
||||
throw DEBUG ? new DecoderException("Bad intent " + handshake.getIntent()) : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
} 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);
|
||||
ctx.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);
|
||||
final ChannelHandler inboundHandler = ctx.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;
|
||||
}
|
||||
|
||||
+18
-17
@@ -25,10 +25,8 @@ 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.api.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -36,7 +34,9 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INACTIVE_LISTENER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
|
||||
import static xyz.jonesdev.sonar.bukkit.SonarBukkit.INITIALIZE_LISTENER;
|
||||
|
||||
// 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
|
||||
@@ -196,8 +196,7 @@ public class FallbackBukkitInjector {
|
||||
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>
|
||||
// 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;
|
||||
}
|
||||
@@ -235,27 +234,29 @@ public class FallbackBukkitInjector {
|
||||
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 (!INITIALIZE_LISTENER.isDone()) {
|
||||
childHandlerField.set(bootstrap, new ChannelInitializer<>() {
|
||||
|
||||
// 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) {
|
||||
protected void initChannel(final @NotNull Channel channel) {
|
||||
channel.close();
|
||||
}
|
||||
};
|
||||
childHandlerField.set(finalBootstrap, initializer);
|
||||
});
|
||||
}
|
||||
|
||||
SonarBukkit.INITIALIZE_LISTENER.thenAccept(v -> {
|
||||
final ChannelHandler _bootstrap = bootstrap;
|
||||
|
||||
INITIALIZE_LISTENER.thenAccept(__ -> {
|
||||
try {
|
||||
childHandlerField.set(finalBootstrap, new FallbackInjectedChannelInitializer(originalInitializer,
|
||||
childHandlerField.set(_bootstrap, new FallbackInjectedChannelInitializer(originalInitializer,
|
||||
pipeline -> {
|
||||
pipeline.addAfter("splitter", FALLBACK_PACKET_DECODER, new FallbackBukkitInboundHandler());
|
||||
pipeline.addFirst(FallbackPipelines.FALLBACK_INACTIVE_LISTENER, new ChannelInactiveListener());
|
||||
pipeline.addFirst(FALLBACK_INACTIVE_LISTENER, new ChannelInactiveListener());
|
||||
}
|
||||
));
|
||||
} catch (IllegalAccessException exception) {
|
||||
|
||||
+1
-2
@@ -34,7 +34,6 @@ 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;
|
||||
@@ -84,7 +83,7 @@ public final class BukkitJoinListener implements Listener {
|
||||
}
|
||||
|
||||
// Make sure the player is not a fake player
|
||||
if (FakeChannelUtil.isFakePlayer(channel)) {
|
||||
if (FakeChannelUtil.isFakeChannel(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
// Make sure to properly stop the metrics
|
||||
if (metrics != null) {
|
||||
// Make sure to properly shutdown bStats metrics
|
||||
metrics.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -26,13 +26,15 @@ import net.md_5.bungee.protocol.PacketWrapper;
|
||||
import net.md_5.bungee.protocol.packet.Handshake;
|
||||
import net.md_5.bungee.protocol.packet.LoginRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.STATUS;
|
||||
|
||||
final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
private static final MethodHandle CHANNEL_WRAPPER_GETTER;
|
||||
@@ -48,20 +50,20 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
// Intercept any packets processed by BungeeCord
|
||||
if (msg instanceof PacketWrapper) {
|
||||
final PacketWrapper packetWrapper = (PacketWrapper) msg;
|
||||
final DefinedPacket wrappedPacket = packetWrapper.packet;
|
||||
// Don't handle any invalid packets
|
||||
// Skip any unknown or 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(channel, handshake.getHost(), handshake.getProtocolVersion());
|
||||
// We don't care about server pings; remove the handler
|
||||
if (handshake.getRequestedProtocol() == 1) {
|
||||
ctx.channel().pipeline().remove(this);
|
||||
if (handshake.getRequestedProtocol() == STATUS) {
|
||||
ctx.pipeline().remove(this);
|
||||
} else {
|
||||
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
|
||||
}
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
@@ -77,7 +79,7 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
}
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.channel().pipeline().remove(this);
|
||||
ctx.pipeline().remove(this);
|
||||
// Make sure to mark this packet as released
|
||||
packetWrapper.trySingleRelease();
|
||||
// Let Sonar process the login packet
|
||||
@@ -86,7 +88,6 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -47,12 +47,10 @@ public class FallbackBungeeInjector {
|
||||
unsafeField.setAccessible(true);
|
||||
final Unsafe unsafe = (Unsafe) unsafeField.get(null);
|
||||
|
||||
// Get the base object (the object containing the field)
|
||||
final Object base = unsafe.staticFieldBase(childField);
|
||||
// Get the offset of the static field within its class
|
||||
final long offset = unsafe.staticFieldOffset(childField);
|
||||
|
||||
// Replace the original channel initializer
|
||||
// Replace the original channel initializer with our new injected initializer
|
||||
unsafe.putObject(base, offset, injectedInitializer);
|
||||
} catch (Exception exception) {
|
||||
Sonar.get().getLogger().error("An error occurred while injecting {}", exception);
|
||||
|
||||
@@ -31,30 +31,32 @@ import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.SonarSupplier;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.database.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.notification.ActionBarNotificationHandler;
|
||||
import xyz.jonesdev.sonar.api.notification.ChatNotificationHandler;
|
||||
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.ratelimit.CaffeineCacheRatelimiter;
|
||||
import xyz.jonesdev.sonar.common.fallback.ratelimit.NoopCacheRatelimiter;
|
||||
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.subcommand.*;
|
||||
import xyz.jonesdev.sonar.common.update.UpdateChecker;
|
||||
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class SonarBootstrap<T> implements Sonar {
|
||||
private T plugin;
|
||||
@Setter
|
||||
private Verbose verboseHandler;
|
||||
private ActionBarNotificationHandler actionBarNotificationHandler;
|
||||
@Setter
|
||||
private Notification notificationHandler;
|
||||
private ChatNotificationHandler chatNotificationHandler;
|
||||
private SonarConfiguration config;
|
||||
private VerifiedPlayerController verifiedPlayerController;
|
||||
private final LibraryManager libraryManager;
|
||||
@@ -79,8 +81,8 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
this.platform = platform;
|
||||
// Load the rest of the components
|
||||
this.statistics = new GlobalSonarStatistics();
|
||||
this.verboseHandler = new Verbose();
|
||||
this.notificationHandler = new Notification();
|
||||
this.actionBarNotificationHandler = new ActionBarNotificationHandler();
|
||||
this.chatNotificationHandler = new ChatNotificationHandler();
|
||||
this.config = new SonarConfiguration(dataDirectory);
|
||||
// Register all subcommands
|
||||
this.subcommandRegistry = new SubcommandRegistry();
|
||||
@@ -148,30 +150,14 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
getLogger().warn("Reloading after changing the database type is not recommended as it may cause data loss.");
|
||||
}
|
||||
|
||||
// Update the CAPTCHA generator if necessary
|
||||
if (getConfig().getVerification().getMap().getTiming() == SonarConfiguration.Verification.Timing.NEVER) {
|
||||
getFallback().setCaptchaGenerator(null);
|
||||
} else if (getFallback().getCaptchaGenerator() == null
|
||||
|| getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
|
||||
getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
|
||||
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
|
||||
} else {
|
||||
getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
|
||||
}
|
||||
|
||||
// Prepare cached packets
|
||||
getLogger().info("Taking cached snapshots of all packets...");
|
||||
FallbackPreparer.prepare();
|
||||
|
||||
// Update ratelimiter caches
|
||||
getFallback().getRatelimiter().setAttemptCache(Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(getConfig().getVerification().getReconnectDelay()))
|
||||
.ticker(Ticker.systemTicker())
|
||||
.build());
|
||||
getFallback().getRatelimiter().setFailCountCache(Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(getConfig().getVerification().getRememberTime()))
|
||||
.ticker(Ticker.systemTicker())
|
||||
.build());
|
||||
// Update ratelimiter cache
|
||||
getFallback().setRatelimiter(getConfig().getVerification().getReconnectDelay() > 0L
|
||||
? new CaffeineCacheRatelimiter(getConfig().getVerification().getReconnectDelay(), TimeUnit.MILLISECONDS)
|
||||
: NoopCacheRatelimiter.INSTANCE);
|
||||
|
||||
// Update blacklist cache
|
||||
final long blacklistTime = getConfig().getVerification().getBlacklistTime();
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics.INCOMING;
|
||||
import static xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics.OUTGOING;
|
||||
import static xyz.jonesdev.sonar.common.statistics.BandwidthStatistics.INCOMING;
|
||||
import static xyz.jonesdev.sonar.common.statistics.BandwidthStatistics.OUTGOING;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
|
||||
+31
-30
@@ -18,19 +18,18 @@
|
||||
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.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 xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -51,8 +50,6 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
protected ProtocolVersion protocolVersion;
|
||||
protected RemovalListener channelRemovalListener = RemovalListener.EMPTY;
|
||||
|
||||
protected static final Fallback FALLBACK = Sonar.get().getFallback();
|
||||
|
||||
/**
|
||||
* Validates and handles incoming handshake packets
|
||||
*
|
||||
@@ -64,11 +61,11 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
final int protocol) throws Exception {
|
||||
// Check if the hostname is invalid
|
||||
if (hostname.isEmpty()) {
|
||||
throw new CorruptedFrameException("Hostname is empty");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
// Check if the player has already sent a handshake packet
|
||||
if (protocolVersion != null) {
|
||||
throw new CorruptedFrameException("Already sent handshake");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
// Store the protocol version
|
||||
protocolVersion = ProtocolVersion.fromId(protocol);
|
||||
@@ -91,13 +88,13 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
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");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
// 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");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
// Increase joins per second for the action bar verbose
|
||||
GlobalSonarStatistics.countLogin();
|
||||
@@ -105,15 +102,10 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
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;
|
||||
}
|
||||
final String hostAddress = socketAddress.getAddress().getHostAddress();
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (FALLBACK.getConnected().containsKey(inboundHandler.getInetAddress())) {
|
||||
if (Sonar.get().getFallback().getConnected().containsKey(inboundHandler.getInetAddress())) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying);
|
||||
return;
|
||||
}
|
||||
@@ -125,6 +117,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player failed the verification too many times
|
||||
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
|
||||
final int limit = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
|
||||
if (score >= limit) {
|
||||
customDisconnect(channel, protocolVersion, blacklisted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
initialLogin(ctx, inboundHandler.getInetAddress(), loginPacket);
|
||||
@@ -141,13 +141,13 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
// 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)) {
|
||||
if (Sonar.get().getVerifiedPlayerController().has(hostAddress, offlineUuid)) {
|
||||
initialLogin(ctx, inboundHandler.getInetAddress(), loginPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!FALLBACK.getRatelimiter().attempt(inboundHandler.getInetAddress())) {
|
||||
if (!Sonar.get().getFallback().getRatelimiter().attempt(inboundHandler.getInetAddress())) {
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast);
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
rewriteProtocol(ctx, channelRemovalListener);
|
||||
|
||||
// Queue the connection for further processing
|
||||
FALLBACK.getQueue().getPlayers().compute(inboundHandler.getInetAddress(), (inetAddress, runnable) -> {
|
||||
Sonar.get().getFallback().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);
|
||||
@@ -217,14 +217,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
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);
|
||||
Sonar.get().getFallback().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);
|
||||
final int onlinePerIp = Sonar.get().getFallback().getOnline().getOrDefault(inetAddress, 0);
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
|
||||
return;
|
||||
@@ -235,7 +235,6 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
loginPacket.run();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect the player before verification (during login)
|
||||
* by replacing the encoder before running the method.
|
||||
@@ -247,26 +246,28 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
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));
|
||||
if (channel.isActive()) {
|
||||
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());
|
||||
// Remove the connection handler pipeline to completely take over the channel
|
||||
final String handler = Sonar.get().getPlatform().getConnectionHandler();
|
||||
if (channel.pipeline().context(handler) != null) {
|
||||
channel.pipeline().remove(handler);
|
||||
}
|
||||
final String encoder = Sonar.get().getPlatform().getEncoderFunction().apply(channel.pipeline());
|
||||
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
|
||||
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
|
||||
// Close the channel if no decoder exists
|
||||
// Close the channel if no encoder exists
|
||||
if (currentEncoder != null) {
|
||||
// We don't need to update the encoder if it's already present
|
||||
// We don't need to update the encoder if it has already been replaced by Sonar
|
||||
if (!(currentEncoder instanceof FallbackPacketEncoder)) {
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
channel.pipeline().replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
+34
-49
@@ -20,7 +20,6 @@ package xyz.jonesdev.sonar.common.fallback;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
@@ -42,6 +41,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.DisconnectPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.session.FallbackLoginSessionHandler;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
@@ -89,20 +89,16 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
@Override
|
||||
public void hijack(final @NotNull String username, final @NotNull UUID offlineUuid) {
|
||||
// The player has joined the verification
|
||||
GlobalSonarStatistics.totalAttemptedVerifications++;
|
||||
|
||||
if (Sonar.get().getConfig().getVerification().isLogConnections()) {
|
||||
// Only log the processing message if the server isn't under attack.
|
||||
// We let the user override this through the configuration.
|
||||
if (Sonar.get().getAttackTracker().getCurrentAttack() == null
|
||||
|| Sonar.get().getConfig().getVerification().isLogDuringAttack()) {
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
|
||||
.replace("<username>", username)
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
|
||||
.replace("<protocol>", String.valueOf(protocolVersion.getProtocol())));
|
||||
}
|
||||
if (Sonar.get().getConfig().getVerification().isLogConnections()
|
||||
&& (Sonar.get().getAttackTracker().getCurrentAttack() == null
|
||||
|| Sonar.get().getConfig().getVerification().isLogDuringAttack())) {
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
|
||||
.replace("<username>", username)
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
|
||||
.replace("<protocol>", String.valueOf(protocolVersion.getProtocol())));
|
||||
}
|
||||
|
||||
// Call the VerifyJoinEvent for external API usage
|
||||
@@ -143,10 +139,10 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
@Override
|
||||
public void fail(final @NotNull String reason) {
|
||||
GlobalSonarStatistics.totalFailedVerifications++;
|
||||
|
||||
disconnect(Sonar.get().getConfig().getVerification().getVerificationFailed());
|
||||
|
||||
// Only log the failed message if the server isn't currently under attack.
|
||||
// However, we let the user override this through the configuration.
|
||||
final boolean shouldLog = Sonar.get().getAttackTracker().getCurrentAttack() == null
|
||||
|| Sonar.get().getConfig().getVerification().isLogDuringAttack();
|
||||
|
||||
@@ -158,49 +154,40 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
.replace("<reason>", reason));
|
||||
}
|
||||
|
||||
// Increment number of total failed verifications
|
||||
GlobalSonarStatistics.totalFailedVerifications++;
|
||||
|
||||
// Call the VerifyFailedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(this, reason));
|
||||
|
||||
// Use a label, so we can easily add more code beneath this method in the future
|
||||
blacklist: {
|
||||
// Check if the player has too many failed attempts
|
||||
final int blacklistThreshold = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
|
||||
final int limit = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
|
||||
// The user is allowed to disable the blacklist entirely by setting the threshold to 0
|
||||
if (blacklistThreshold <= 0) break blacklist;
|
||||
if (limit <= 0) break blacklist;
|
||||
|
||||
// Use 1 as the default amount of fails since we haven't cached anything yet
|
||||
final int fails = Sonar.get().getFallback().getRatelimiter().getFailCountCache().get(inetAddress, ignored -> 1);
|
||||
// Now we simply need to check if the threshold is reached
|
||||
if (fails < blacklistThreshold) {
|
||||
// Make sure we increment the number of fails
|
||||
Sonar.get().getFallback().getRatelimiter().incrementFails(inetAddress, fails);
|
||||
break blacklist;
|
||||
}
|
||||
final String hostAddress = inetAddress.getHostAddress();
|
||||
final int score = Sonar.get().getFallback().getBlacklist().get(hostAddress, __ -> 0);
|
||||
final int newScore = score + 1;
|
||||
|
||||
Sonar.get().getFallback().getBlacklist().put(hostAddress, newScore);
|
||||
|
||||
// The player hasn't been blacklisted yet, so skip this iteration
|
||||
if (newScore < limit) break blacklist;
|
||||
|
||||
GlobalSonarStatistics.totalBlacklistedPlayers++;
|
||||
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
|
||||
// Increment number of total blacklisted players
|
||||
GlobalSonarStatistics.totalBlacklistedPlayers++;
|
||||
|
||||
Sonar.get().getFallback().getBlacklist().put(getInetAddress(), (byte) 0);
|
||||
|
||||
if (shouldLog) {
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("<protocol>", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
}
|
||||
|
||||
// Invalidate the cached entry to ensure memory safety
|
||||
Sonar.get().getFallback().getRatelimiter().getFailCountCache().invalidate(inetAddress);
|
||||
}
|
||||
|
||||
// Throw an exception to avoid further code execution
|
||||
throw new CorruptedFrameException();
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,18 +198,16 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
public static void closeWith(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull Object msg) {
|
||||
if (channel.isActive()) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
|
||||
channel.eventLoop().execute(() -> {
|
||||
channel.config().setAutoRead(false);
|
||||
channel.eventLoop().schedule(() -> {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}, 250L, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
} else {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
|
||||
channel.eventLoop().execute(() -> {
|
||||
channel.config().setAutoRead(false);
|
||||
channel.eventLoop().schedule(() -> {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}, 250L, TimeUnit.MILLISECONDS);
|
||||
});
|
||||
} else {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -18,14 +18,13 @@
|
||||
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.api.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
|
||||
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
|
||||
|
||||
@@ -63,11 +62,11 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
|
||||
|
||||
// Inject Sonar's channel handler into the pipeline;
|
||||
// Also make sure the player is not a fake player to avoid compatibility issues
|
||||
if (channel.isActive() && !FakeChannelUtil.isFakePlayer(channel)) {
|
||||
final ChannelHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
|
||||
if (channel.isActive() && !FakeChannelUtil.isFakeChannel(channel)) {
|
||||
final FallbackInboundHandler 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());
|
||||
final String encoder = Sonar.get().getPlatform().getEncoder().apply(channel.pipeline());
|
||||
channel.pipeline().addBefore(encoder, FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
} else {
|
||||
channel.pipeline().addFirst(FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ 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 {
|
||||
|
||||
+3
-4
@@ -20,12 +20,11 @@ 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 xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -58,7 +57,7 @@ public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
if (readVarInt < 0) {
|
||||
in.clear();
|
||||
throw new CorruptedFrameException("Invalid VarInt length " + readVarInt);
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
} else if (readVarInt == 0) {
|
||||
// Skip over the empty packet(s) and ignore it
|
||||
in.readerIndex(end + 1);
|
||||
@@ -71,7 +70,7 @@ public final class FallbackVarInt21FrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
} else if (reader.getResult() == FallbackVarInt21ByteDecoder.DecodeResult.TOO_BIG) {
|
||||
in.clear();
|
||||
throw new CorruptedFrameException("Too big VarInt " + reader.getReadVarInt());
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -26,7 +26,6 @@ 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> {
|
||||
|
||||
+7
-5
@@ -20,15 +20,17 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
|
||||
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
@@ -76,12 +78,12 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
// Try to decode the packet for the given protocol version
|
||||
packet.decode(byteBuf, protocolVersion);
|
||||
} catch (Throwable throwable) {
|
||||
throw new CorruptedFrameException("Failed to decode packet");
|
||||
throw DEBUG ? new DecoderException(throwable) : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
// Check if the packet still has bytes left after we decoded it
|
||||
if (byteBuf.isReadable()) {
|
||||
throw new CorruptedFrameException("Could not read packet to end");
|
||||
throw DEBUG ? new DecoderException("Could not read packet to end") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
// Let our verification handler process the packet
|
||||
@@ -102,12 +104,12 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
final @NotNull FallbackPacket packet) throws Exception {
|
||||
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, protocolVersion);
|
||||
if (expectedMaxLen != -1 && byteBuf.readableBytes() > expectedMaxLen) {
|
||||
throw new CorruptedFrameException("Packet too large");
|
||||
throw DEBUG ? new DecoderException("Packet too large") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
final int expectedMinLen = packet.expectedMinLength(byteBuf, protocolVersion);
|
||||
if (expectedMinLen != -1 && byteBuf.readableBytes() < expectedMinLen) {
|
||||
throw new CorruptedFrameException("Packet too small");
|
||||
throw DEBUG ? new DecoderException("Packet too small") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -37,7 +37,6 @@ import java.util.function.Supplier;
|
||||
|
||||
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 {
|
||||
|
||||
-1
@@ -29,7 +29,6 @@ import java.util.Map;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.ID_TO_PROTOCOL_CONSTANT;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/PacketSnapshot.java
|
||||
@Getter
|
||||
public final class FallbackPacketSnapshot implements FallbackPacket {
|
||||
|
||||
+11
@@ -22,6 +22,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
|
||||
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;
|
||||
@@ -210,6 +211,15 @@ public class FallbackPreparer {
|
||||
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, 0));
|
||||
}
|
||||
|
||||
// Update the CAPTCHA generator if necessary
|
||||
if (Sonar.get().getFallback().getCaptchaGenerator() == null
|
||||
|| Sonar.get().getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
|
||||
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
|
||||
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
|
||||
} else {
|
||||
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
|
||||
}
|
||||
|
||||
// Prepare CAPTCHA answers
|
||||
CaptchaPreparer.prepare();
|
||||
} else {
|
||||
@@ -217,6 +227,7 @@ public class FallbackPreparer {
|
||||
enterCodeMessage = null;
|
||||
incorrectCaptcha = null;
|
||||
xpCountdown = null;
|
||||
Sonar.get().getFallback().setCaptchaGenerator(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -29,7 +29,6 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionRegistry.*;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/packets/play/PacketJoinGame.java
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
|
||||
+3
-3
@@ -18,12 +18,12 @@
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.Getter;
|
||||
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.util.exception.QuietDecoderException;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readExtendedForgeShort;
|
||||
@@ -50,12 +50,12 @@ public final class PluginMessagePacket implements FallbackPacket {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
length = byteBuf.readableBytes();
|
||||
if (length > Short.MAX_VALUE) {
|
||||
throw new CorruptedFrameException("Got too much data: " + length);
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
} else {
|
||||
length = readExtendedForgeShort(byteBuf);
|
||||
if (length > FORGE_MAX_ARRAY_LENGTH) {
|
||||
throw new CorruptedFrameException("Got too much data: " + length);
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -18,7 +18,6 @@
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -26,6 +25,7 @@ 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.util.ComponentHolder;
|
||||
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -83,18 +83,18 @@ public final class SystemChatPacket implements FallbackPacket {
|
||||
|| saltLong == 0L) && signatureBytes.length == 0) {
|
||||
unsigned = true;
|
||||
} else {
|
||||
throw new CorruptedFrameException("Invalid signature");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
final boolean signedPreview = byteBuf.readBoolean();
|
||||
if (signedPreview && unsigned) {
|
||||
throw new CorruptedFrameException("Signature missing");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
final int size = readVarInt(byteBuf);
|
||||
if (size < 0 || size > 5) {
|
||||
throw new CorruptedFrameException("Invalid previous messages");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.ratelimit;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.Ticker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
// Idea taken from Velocity
|
||||
@Setter
|
||||
@RequiredArgsConstructor
|
||||
public final class CaffeineCacheRatelimiter implements Ratelimiter<InetAddress> {
|
||||
private Cache<InetAddress, Long> expiringCache;
|
||||
private long timeout;
|
||||
|
||||
public CaffeineCacheRatelimiter(final long timeout, final @NotNull TimeUnit unit) {
|
||||
this.expiringCache = Caffeine.newBuilder()
|
||||
.ticker(Ticker.systemTicker())
|
||||
.expireAfterWrite(timeout, unit)
|
||||
.build();
|
||||
this.timeout = unit.toNanos(timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attempt(final @NotNull InetAddress inetAddress) {
|
||||
final long expectedNewTimestamp = System.nanoTime() + timeout;
|
||||
final long last = expiringCache.get(inetAddress, result -> expectedNewTimestamp);
|
||||
return expectedNewTimestamp == last;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.ratelimit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.ratelimit.Ratelimiter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
// Idea taken from Velocity
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class NoopCacheRatelimiter implements Ratelimiter<InetAddress> {
|
||||
public static final NoopCacheRatelimiter INSTANCE = new NoopCacheRatelimiter();
|
||||
|
||||
@Override
|
||||
public boolean attempt(final @NotNull InetAddress inetAddress) {
|
||||
// Accept all connections
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -17,7 +17,6 @@
|
||||
|
||||
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;
|
||||
@@ -26,6 +25,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
|
||||
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 xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
// Disconnect the player if there is no CAPTCHA available at the moment
|
||||
if (!CaptchaPreparer.isCaptchaAvailable()) {
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
|
||||
throw new CorruptedFrameException("No captcha available");
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
|
||||
|
||||
+1
-1
@@ -20,9 +20,9 @@ package xyz.jonesdev.sonar.common.fallback.session;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.database.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketListener;
|
||||
|
||||
+5
-7
@@ -20,7 +20,7 @@ package xyz.jonesdev.sonar.common.service;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
import xyz.jonesdev.sonar.common.statistics.BandwidthStatistics;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -43,21 +43,19 @@ public final class SonarServiceManager {
|
||||
|
||||
public void start() {
|
||||
VERBOSE.scheduleAtFixedRate(() -> {
|
||||
// Make sure to clean up the cached statistics
|
||||
// since we don't want to display wrong values.
|
||||
// Make sure to clean up the cached statistics since we don't want to display wrong values
|
||||
Sonar.get().getStatistics().cleanUpCache();
|
||||
Sonar.get().getFallback().getRatelimiter().getFailCountCache().cleanUp();
|
||||
Sonar.get().getFallback().getBlacklist().cleanUp();
|
||||
// Update the attack tracker
|
||||
Sonar.get().getAttackTracker().checkIfUnderAttack();
|
||||
// Update the action bar verbose
|
||||
Sonar.get().getVerboseHandler().observe();
|
||||
// Publish the action bar notifications
|
||||
Sonar.get().getActionBarNotificationHandler().handleNotification();
|
||||
}, 0L, 250L, TimeUnit.MILLISECONDS);
|
||||
|
||||
FALLBACK_QUEUE.scheduleAtFixedRate(() -> Sonar.get().getFallback().getQueue().poll(),
|
||||
1L, 1L, TimeUnit.SECONDS);
|
||||
|
||||
STATISTICS.scheduleAtFixedRate(CachedBandwidthStatistics::reset,
|
||||
STATISTICS.scheduleAtFixedRate(BandwidthStatistics::reset,
|
||||
0L, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -19,13 +19,13 @@ package xyz.jonesdev.sonar.common.statistics;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import xyz.jonesdev.sonar.api.jvm.JVMProcessInformation;
|
||||
import xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler;
|
||||
|
||||
/**
|
||||
* This counts all incoming and outgoing traffic.
|
||||
*/
|
||||
@Getter
|
||||
public enum CachedBandwidthStatistics {
|
||||
public enum BandwidthStatistics {
|
||||
INCOMING,
|
||||
OUTGOING;
|
||||
|
||||
@@ -54,8 +54,8 @@ public enum CachedBandwidthStatistics {
|
||||
|
||||
private void cacheAndReset() {
|
||||
ttl += curr; // increment total
|
||||
cachedSecond = JVMProcessInformation.formatMemory(curr);
|
||||
cachedTtl = JVMProcessInformation.formatMemory(ttl);
|
||||
cachedSecond = SimpleProcessProfiler.formatMemory(curr);
|
||||
cachedTtl = SimpleProcessProfiler.formatMemory(ttl);
|
||||
curr = 0L; // reset current
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -80,42 +80,42 @@ public final class GlobalSonarStatistics implements SonarStatistics {
|
||||
|
||||
@Override
|
||||
public long getCurrentIncomingBandwidth() {
|
||||
return CachedBandwidthStatistics.INCOMING.getCurr();
|
||||
return BandwidthStatistics.INCOMING.getCurr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentOutgoingBandwidth() {
|
||||
return CachedBandwidthStatistics.OUTGOING.getCurr();
|
||||
return BandwidthStatistics.OUTGOING.getCurr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalIncomingBandwidth() {
|
||||
return CachedBandwidthStatistics.INCOMING.getTtl();
|
||||
return BandwidthStatistics.INCOMING.getTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalOutgoingBandwidth() {
|
||||
return CachedBandwidthStatistics.OUTGOING.getTtl();
|
||||
return BandwidthStatistics.OUTGOING.getTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentIncomingBandwidthFormatted() {
|
||||
return CachedBandwidthStatistics.INCOMING.getCachedSecond();
|
||||
return BandwidthStatistics.INCOMING.getCachedSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentOutgoingBandwidthFormatted() {
|
||||
return CachedBandwidthStatistics.OUTGOING.getCachedSecond();
|
||||
return BandwidthStatistics.OUTGOING.getCachedSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotalIncomingBandwidthFormatted() {
|
||||
return CachedBandwidthStatistics.INCOMING.getCachedTtl();
|
||||
return BandwidthStatistics.INCOMING.getCachedTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotalOutgoingBandwidthFormatted() {
|
||||
return CachedBandwidthStatistics.OUTGOING.getCachedTtl();
|
||||
return BandwidthStatistics.OUTGOING.getCachedTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+32
-55
@@ -15,10 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -27,9 +25,6 @@ import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "blacklist",
|
||||
arguments = {"add", "remove", "clear", "size"}
|
||||
@@ -49,38 +44,29 @@ public final class BlacklistCommand extends Subcommand {
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
try {
|
||||
// Try to resolve the IP address, so we can actually do something with it
|
||||
final InetAddress inetAddress = InetAddress.getAllByName(rawAddress)[0];
|
||||
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (Sonar.get().getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
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()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
Sonar.get().getFallback().getBlacklist().put(inetAddress, (byte) 0);
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSource().sendMessage(Component.text(
|
||||
"Unexpected error, check console.", NamedTextColor.RED));
|
||||
exception.printStackTrace(System.err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add-warning"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
Sonar.get().getFallback().getBlacklist().put(rawAddress, 1337 /* arbitrarily high number */);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,29 +80,20 @@ public final class BlacklistCommand extends Subcommand {
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
try {
|
||||
// Try to resolve the IP address, so we can actually do something with it
|
||||
final InetAddress inetAddress = InetAddress.getAllByName(rawAddress)[0];
|
||||
|
||||
// Make sure the IP is blacklisted
|
||||
if (!Sonar.get().getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
Sonar.get().getFallback().getBlacklist().invalidate(inetAddress);
|
||||
// Make sure the IP is blacklisted
|
||||
if (!Sonar.get().getFallback().getBlacklist().asMap().containsKey(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSource().sendMessage(Component.text(
|
||||
"Unexpected error, check console.", NamedTextColor.RED));
|
||||
exception.printStackTrace(System.err);
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
Sonar.get().getFallback().getBlacklist().invalidate(rawAddress);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
break;
|
||||
}
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -32,7 +32,7 @@ import java.lang.management.RuntimeMXBean;
|
||||
import java.util.List;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
|
||||
|
||||
// This command is only used to display helpful information to help
|
||||
// the Sonar contributors to fix issues more quickly
|
||||
+4
-4
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
@@ -33,15 +33,15 @@ public final class NotifyCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
if (Sonar.get().getNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getNotificationHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
if (Sonar.get().getChatNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getChatNotificationHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.unsubscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
Sonar.get().getNotificationHandler().subscribe(invocation.getSource().getUuid());
|
||||
Sonar.get().getChatNotificationHandler().subscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.subscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
+7
-7
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@@ -26,10 +26,10 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
import xyz.jonesdev.sonar.common.statistics.BandwidthStatistics;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.profiler.SimpleProcessProfiler.*;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "statistics",
|
||||
@@ -109,10 +109,10 @@ public final class StatisticsCommand extends Subcommand {
|
||||
case "network": {
|
||||
placeholders = new TagResolver.Single[]{
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("incoming-traffic", CachedBandwidthStatistics.INCOMING.getCachedSecond()),
|
||||
Placeholder.unparsed("outgoing-traffic", CachedBandwidthStatistics.OUTGOING.getCachedSecond()),
|
||||
Placeholder.unparsed("incoming-traffic-ttl", CachedBandwidthStatistics.INCOMING.getCachedTtl()),
|
||||
Placeholder.unparsed("outgoing-traffic-ttl", CachedBandwidthStatistics.OUTGOING.getCachedTtl())
|
||||
Placeholder.unparsed("incoming-traffic", BandwidthStatistics.INCOMING.getCachedSecond()),
|
||||
Placeholder.unparsed("outgoing-traffic", BandwidthStatistics.OUTGOING.getCachedSecond()),
|
||||
Placeholder.unparsed("incoming-traffic-ttl", BandwidthStatistics.INCOMING.getCachedTtl()),
|
||||
Placeholder.unparsed("outgoing-traffic-ttl", BandwidthStatistics.OUTGOING.getCachedTtl())
|
||||
};
|
||||
break;
|
||||
}
|
||||
+4
-4
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@@ -34,8 +34,8 @@ public final class VerboseCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
if (Sonar.get().getVerboseHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getVerboseHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
if (Sonar.get().getActionBarNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getActionBarNotificationHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
// Reset ActionBar component when unsubscribing
|
||||
invocation.getSource().getAudience().sendActionBar(Component.empty());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
@@ -44,7 +44,7 @@ public final class VerboseCommand extends Subcommand {
|
||||
return;
|
||||
}
|
||||
|
||||
Sonar.get().getVerboseHandler().subscribe(invocation.getSource().getUuid());
|
||||
Sonar.get().getActionBarNotificationHandler().subscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.subscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
+8
-10
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
package xyz.jonesdev.sonar.common.subcommand;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
@@ -24,7 +24,7 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.api.database.model.VerifiedPlayer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
@@ -44,6 +44,7 @@ public final class VerifiedCommand extends Subcommand {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove duplicate code
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
@@ -76,21 +77,20 @@ public final class VerifiedCommand extends Subcommand {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove duplicate code
|
||||
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(inetAddress)) {
|
||||
if (!Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
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(inetAddress);
|
||||
Sonar.get().getVerifiedPlayerController().remove(rawAddress);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
@@ -107,8 +107,6 @@ 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];
|
||||
@@ -116,7 +114,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(inetAddress, uuid)) {
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress, uuid)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.already"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
@@ -125,7 +123,7 @@ public final class VerifiedCommand extends Subcommand {
|
||||
|
||||
// Add verified player to the database
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(inetAddress, uuid, timestamp));
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(rawAddress, uuid, timestamp));
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.add"),
|
||||
@@ -43,7 +43,6 @@ public final class ComponentHolder {
|
||||
this.serializedComponent = GsonComponentSerializer.gson().serialize(component);
|
||||
}
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
|
||||
private BinaryTag serialize(final JsonElement json) {
|
||||
if (json instanceof JsonPrimitive) {
|
||||
|
||||
@@ -26,10 +26,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class FakeChannelUtil {
|
||||
|
||||
/**
|
||||
* @param channel Channel of the player
|
||||
* @return Whether the channel is spoofed/faked
|
||||
* Checks if the provided channel is a fake channel, typically used by player spoofing plugins.
|
||||
*
|
||||
* @param channel The channel to be checked.
|
||||
* @return true if the channel is identified as a fake channel
|
||||
*/
|
||||
public boolean isFakePlayer(final @NotNull Channel channel) {
|
||||
public boolean isFakeChannel(final @NotNull Channel channel) {
|
||||
final String simpleClassName = channel.getClass().getSimpleName();
|
||||
// Player spoof plugins use fake channels (e.g., Spoof Engine uses "FakeChannel")
|
||||
return "FakeChannel".equals(simpleClassName) || "SpoofedChannel".equals(simpleClassName);
|
||||
|
||||
@@ -46,7 +46,6 @@ import java.util.UUID;
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
|
||||
/**
|
||||
* This is a modified FastUUID implementation. The primary difference is that it does not dash its
|
||||
|
||||
@@ -29,13 +29,12 @@ 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");
|
||||
|
||||
/**
|
||||
* @param channel Channel of the player
|
||||
* @return Whether the player is on GeyserMC or not
|
||||
*/
|
||||
public boolean isGeyserConnection(final @NotNull Channel channel,
|
||||
final @NotNull InetSocketAddress originalAddress) {
|
||||
return originalAddress.getPort() == 0 // check for floodgate on the server
|
||||
|| channel.attr(PLAYER_ATTRIBUTE).get() != null; // check for standalone Geyser proxy connection
|
||||
public boolean isGeyserConnection(final @NotNull Channel channel, final @NotNull InetSocketAddress originalAddress) {
|
||||
/*
|
||||
* This method performs two checks:
|
||||
* 1. It checks if the player is joining through Floodgate, which is installed on the server.
|
||||
* 2. It checks if the player is joining through a Floodgate proxy hosted externally.
|
||||
*/
|
||||
return originalAddress.getPort() == 0 || channel.attr(PLAYER_ATTRIBUTE).get() != null;
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,13 @@ package xyz.jonesdev.sonar.common.util;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
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;
|
||||
import xyz.jonesdev.sonar.common.util.exception.QuietDecoderException;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
@@ -56,10 +57,10 @@ import java.util.BitSet;
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
|
||||
@UtilityClass
|
||||
public class ProtocolUtil {
|
||||
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";
|
||||
|
||||
@@ -85,7 +86,7 @@ public class ProtocolUtil {
|
||||
public static int readVarInt(final ByteBuf byteBuf) {
|
||||
int read = readVarIntSafely(byteBuf);
|
||||
if (read == Integer.MIN_VALUE) {
|
||||
throw new CorruptedFrameException("Corrupt VarInt");
|
||||
throw DEBUG ? new DecoderException("Bad VarInt") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -224,33 +225,33 @@ public class ProtocolUtil {
|
||||
|
||||
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf, final int cap) {
|
||||
int length = readVarInt(byteBuf);
|
||||
checkFrame(length >= 0, "Got a negative-length array");
|
||||
checkFrame(length <= cap, "Bad array size");
|
||||
checkFrame(byteBuf.isReadable(length), "Trying to read an array that is too long");
|
||||
checkState(length >= 0, "Got a negative-length array");
|
||||
checkState(length <= cap, "Bad array size");
|
||||
checkState(byteBuf.isReadable(length), "Trying to read an array that is too long");
|
||||
byte[] array = new byte[length];
|
||||
byteBuf.readBytes(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf byteBuf) throws CorruptedFrameException {
|
||||
public static @NotNull String readString(final ByteBuf byteBuf) throws DecoderException {
|
||||
return readString(byteBuf, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf byteBuf,
|
||||
final int cap) throws CorruptedFrameException {
|
||||
final int cap) throws DecoderException {
|
||||
final int length = readVarInt(byteBuf);
|
||||
return readString(byteBuf, cap, length);
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final @NotNull ByteBuf byteBuf,
|
||||
final int cap,
|
||||
final int length) throws CorruptedFrameException {
|
||||
checkFrame(length >= 0, "Got a negative-length string");
|
||||
checkFrame(length <= cap * 3, "Bad string size");
|
||||
checkFrame(byteBuf.isReadable(length), "Tried to read a too-long string");
|
||||
final int length) throws DecoderException {
|
||||
checkState(length >= 0, "Got a negative-length string");
|
||||
checkState(length <= cap * 3, "Bad string size");
|
||||
checkState(byteBuf.isReadable(length), "Tried to read a too-long string");
|
||||
final String str = byteBuf.toString(byteBuf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
byteBuf.readerIndex(byteBuf.readerIndex() + length);
|
||||
checkFrame(str.length() <= cap, "Got a too-long string");
|
||||
checkState(str.length() <= cap, "Got a too-long string");
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -273,7 +274,7 @@ public class ProtocolUtil {
|
||||
}
|
||||
|
||||
public static void writeArray(final ByteBuf byteBuf, final byte @NotNull [] bytes) {
|
||||
checkFrame(bytes.length < Short.MAX_VALUE, "Too long array");
|
||||
checkState(bytes.length < Short.MAX_VALUE, "Too long array");
|
||||
writeVarInt(byteBuf, bytes.length);
|
||||
byteBuf.writeBytes(bytes);
|
||||
}
|
||||
@@ -285,7 +286,6 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/ByteMessage.java#L276
|
||||
public <E extends Enum<E>> void writeEnumSet(final ByteBuf byteBuf,
|
||||
final EnumSet<E> enumset,
|
||||
@@ -315,7 +315,6 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/ByteMessage.java#L219
|
||||
public static void writeNamelessCompoundTag(final @NotNull ByteBuf byteBuf, final @NotNull BinaryTag binaryTag) {
|
||||
try (final ByteBufOutputStream output = new ByteBufOutputStream(byteBuf)) {
|
||||
@@ -364,9 +363,9 @@ public class ProtocolUtil {
|
||||
return ((high & 0xFF) << 15) | low;
|
||||
}
|
||||
|
||||
private void checkFrame(final boolean expression, final String message) {
|
||||
private void checkState(final boolean expression, final String message) {
|
||||
if (!expression) {
|
||||
throw new CorruptedFrameException(message);
|
||||
throw DEBUG ? new DecoderException(message) : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.util.exception;
|
||||
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
|
||||
public final class QuietDecoderException extends DecoderException {
|
||||
public static final QuietDecoderException INSTANCE = new QuietDecoderException();
|
||||
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ 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.'
|
||||
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
|
||||
|
||||
@@ -182,7 +182,7 @@ verification:
|
||||
# Übersetzungen für alle Debug-Nachrichten während der Überprüfung
|
||||
logs:
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein neuer Spieler den Server betretet
|
||||
connection: '<username><ip> (<protocol>) hat sich verbunden.'
|
||||
connection: '<username>/<ip> (<protocol>) hat sich verbunden.'
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein Spieler die Verifizierung nicht besteht
|
||||
failed: '<ip> (<protocol>) hat den Bot-Check nicht bestanden wegen: <reason>'
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein Spieler aufgrund zu vieler fehlgeschlagener Versuche auf die Blacklist gesetzt wird
|
||||
|
||||
@@ -181,7 +181,7 @@ 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>) has connected.'
|
||||
connection: '<username>/<ip> (<protocol>) has connected.'
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '<ip> (<protocol>) has failed the bot check for: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
|
||||
@@ -181,7 +181,7 @@ 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>) se ha conectado.'
|
||||
connection: '<username>/<ip> (<protocol>) se ha conectado.'
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '<ip> (<protocol>) no ha pasado la verificación de bot por: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# Traductions pour tous les messages de débogage pendant la vérification
|
||||
logs:
|
||||
# Message enregistré dans la console chaque fois qu'un nouveau joueur rejoint le serveur
|
||||
connection: "<username><ip> (<protocol>) s'est connecté."
|
||||
connection: "<username>/<ip> (<protocol>) s'est connecté."
|
||||
# Message enregistré dans la console chaque fois qu'un joueur échoue à la vérification
|
||||
failed: '<ip> (<protocol>) a échoué au test du bot pour : <reason>'
|
||||
# Message enregistré dans la console chaque fois qu'un joueur est sur liste noire
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# Terjemahan untuk semua pesan debug selama proses verifikasi
|
||||
logs:
|
||||
# Pesan yang dicatat ke konsol setiap kali pemain baru bergabung ke server
|
||||
connection: '<username><ip> (<protocol>) telah terhubung.'
|
||||
connection: '<username>/<ip> (<protocol>) telah terhubung.'
|
||||
# Pesan yang dicatat ke konsol setiap kali pemain gagal verifikasi
|
||||
failed: '<ip> (<protocol>) telah gagal dalam pemeriksaan bot untuk: <reason>'
|
||||
# Pesan yang dicatat ke konsol setiap kali pemain masuk daftar hitam
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# თარგმანი ყველა დეტალური ინფორმაციის მესიჯებისა
|
||||
logs:
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა ახალი მოთამაშე შემოდის
|
||||
connection: '<username><ip> (<protocol>) დაკავშირდა.'
|
||||
connection: '<username>/<ip> (<protocol>) დაკავშირდა.'
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა მოთამაშე წარუმატებლად ასრულებს შემოწმებას
|
||||
failed: '<ip> (<protocol>) წარუმატებლად გაირა შემოწმება: <reason>'
|
||||
# მესიჯი ჩაწერილი კონსოლში, როცა მოთამაშე შავ ნუსხაშია
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# Vertalingen voor alle debug-berichten tijdens de verificatie
|
||||
logs:
|
||||
# Bericht dat in de console wordt geregistreerd wanneer een nieuwe speler zich bij de server voegt
|
||||
connection: '<username><ip> (<protocol>) heeft verbinding gemaakt.'
|
||||
connection: '<username>/<ip> (<protocol>) heeft verbinding gemaakt.'
|
||||
# Bericht dat in de console wordt geregistreerd wanneer de verificatie van een speler mislukt
|
||||
failed: '<ip> (<protocol>) heeft de bot-controle niet doorstaan vanwege: <reason>'
|
||||
# Bericht dat op de console wordt geregistreerd wanneer een speler op de zwarte lijst staat
|
||||
|
||||
@@ -184,7 +184,7 @@ 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ę.'
|
||||
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
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# Переводы всех консольных сообщений во время проверки
|
||||
logs:
|
||||
# Сообщение, которое показыватся когда на сервер заходит новый игрок
|
||||
connection: '<username><ip> (<protocol>) подключился.'
|
||||
connection: '<username>/<ip> (<protocol>) подключился.'
|
||||
# Сообщение, которое показывается когда кто-то не прошел проверку на бота
|
||||
failed: '<ip> (<protocol>) не прошел проверку бота на: <reason>'
|
||||
# Сообщение, которое показывается когда кто-то было занесен в черный список
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# Doğrulama sırasında tüm hata ayıklama mesajları için çeviriler
|
||||
logs:
|
||||
# Sunucuya yeni bir oyuncu katıldığında konsola kaydedilen mesaj
|
||||
connection: '<username><ip> (<protocol>) sunucuya bağlandı.'
|
||||
connection: '<username>/<ip> (<protocol>) sunucuya bağlandı.'
|
||||
# Bir oyuncu doğrulamada başarısız olduğunda konsola kaydedilen mesaj
|
||||
failed: '<ip> (<protocol>) bot korumasında başarısız oldu: <reason>'
|
||||
# Bir oyuncu kara listeye alındığında konsola kaydedilen mesaj
|
||||
|
||||
@@ -181,7 +181,7 @@ verification:
|
||||
# 验证过程中向控制台打印的调试信息
|
||||
logs:
|
||||
# 当玩家进入服务器并验证时显示的消息
|
||||
connection: '<username><ip> (<protocol>) 连接到了服务器.'
|
||||
connection: '<username>/<ip> (<protocol>) 连接到了服务器.'
|
||||
# 当玩家未通过检查时打印的消息
|
||||
failed: '<ip> (<protocol>) 由于原因 <reason> 无法通过检查.'
|
||||
# 当玩家被自动列入黑名单时打印的消息
|
||||
|
||||
@@ -108,8 +108,8 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
// Make sure to properly stop the metrics
|
||||
if (metrics != null) {
|
||||
// Make sure to properly shutdown bStats metrics
|
||||
metrics.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-12
@@ -27,31 +27,28 @@ import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.STATUS;
|
||||
|
||||
final class FallbackVelocityInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
// Intercept any handshake packet by the client
|
||||
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);
|
||||
if (handshake.getNextStatus() == STATUS) {
|
||||
ctx.pipeline().remove(this);
|
||||
} else {
|
||||
handleHandshake(ctx.channel(), handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
|
||||
}
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
else if (msg instanceof ServerLoginPacket serverLogin) {
|
||||
} 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 MinecraftConnection minecraftConnection = (MinecraftConnection) ctx.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
|
||||
// Deject this pipeline and let Sonar process the login packet
|
||||
ctx.pipeline().remove(this);
|
||||
handleLogin(ctx.channel(), ctx, () -> ctx.fireChannelRead(msg), serverLogin.getUsername(), socketAddress);
|
||||
return;
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ import com.velocitypowered.proxy.network.ServerChannelInitializerHolder;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
Reference in new issue
Block a user