mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 09:20:15 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c8106eaf6 | ||
|
|
92ac5fbeb2 | ||
|
|
b800a2d154 | ||
|
|
1957463b81 | ||
|
|
25114eabe5 | ||
|
|
a45b526ffb | ||
|
|
7b70d32e84 | ||
|
|
e84c1a54db | ||
|
|
fd869ac199 | ||
|
|
abf1479a84 | ||
|
|
c00392e062 |
No files matched your search
@@ -97,6 +97,6 @@ public interface Sonar {
|
||||
|
||||
@NotNull
|
||||
static Sonar get() {
|
||||
return SonarSupplier.get();
|
||||
return SonarSupplier.sonar;
|
||||
}
|
||||
}
|
||||
@@ -19,20 +19,15 @@ package xyz.jonesdev.sonar.api;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.exception.AlreadyInitializedException;
|
||||
|
||||
@UtilityClass
|
||||
public class SonarSupplier {
|
||||
private Sonar _sonar;
|
||||
Sonar sonar;
|
||||
|
||||
public void set(final @NotNull Sonar instance) {
|
||||
if (_sonar != null) {
|
||||
throw new AlreadyInitializedException("Sonar is already initialized");
|
||||
public void set(final @NotNull Sonar _sonar) {
|
||||
if (sonar != null) {
|
||||
throw new IllegalStateException("Sonar is already initialized");
|
||||
}
|
||||
_sonar = instance;
|
||||
}
|
||||
|
||||
public @NotNull Sonar get() {
|
||||
return _sonar;
|
||||
sonar = _sonar;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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.exception;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class AlreadyInitializedException extends RuntimeException {
|
||||
public AlreadyInitializedException(final @NotNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -29,10 +28,7 @@ import java.net.InetAddress;
|
||||
|
||||
public interface FallbackUser {
|
||||
@NotNull
|
||||
Channel getChannel();
|
||||
|
||||
@NotNull
|
||||
ChannelPipeline getPipeline();
|
||||
Channel channel();
|
||||
|
||||
@NotNull
|
||||
InetAddress getInetAddress();
|
||||
@@ -69,8 +65,8 @@ public interface FallbackUser {
|
||||
* @param msg Message to send to the player
|
||||
*/
|
||||
default void write(final @NotNull Object msg) {
|
||||
if (getChannel().isActive()) {
|
||||
getChannel().writeAndFlush(msg, getChannel().voidPromise());
|
||||
if (channel().isActive()) {
|
||||
channel().writeAndFlush(msg, channel().voidPromise());
|
||||
} else {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
@@ -81,8 +77,8 @@ public interface FallbackUser {
|
||||
* sent once all messages are flushed.
|
||||
*/
|
||||
default void delayedWrite(final @NotNull Object msg) {
|
||||
if (getChannel().isActive()) {
|
||||
getChannel().write(msg, getChannel().voidPromise());
|
||||
if (channel().isActive()) {
|
||||
channel().write(msg, channel().voidPromise());
|
||||
} else {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
|
||||
+8
-9
@@ -34,12 +34,14 @@ import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
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 {
|
||||
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
|
||||
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
FallbackBukkitInboundHandler() {
|
||||
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
|
||||
@@ -53,17 +55,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
|
||||
final @NotNull ProtocolVersion protocolVersion) {
|
||||
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
|
||||
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
if (msg instanceof ByteBuf) {
|
||||
final ByteBuf byteBuf = (ByteBuf) msg;
|
||||
|
||||
@@ -119,7 +117,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
case LOGIN:
|
||||
case TRANSFER:
|
||||
// Let the actual handler know about the handshake packet
|
||||
handleHandshake(ctx.channel(), handshake.getHostname(), handshake.getProtocolVersionId());
|
||||
handleHandshake(ctx, handshake.getHostname(), handshake.getProtocolVersionId());
|
||||
// Be ready for the next packet (which is supposed to be a login packet)
|
||||
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
|
||||
break;
|
||||
@@ -132,9 +130,10 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.pipeline().remove(this);
|
||||
// Let Sonar process the login packet
|
||||
handleLogin(ctx.channel(), ctx, () -> {
|
||||
handleLogin(ctx, () -> {
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
ctx.fireChannelRead(byteBuf.retain());
|
||||
// TODO: recode this?
|
||||
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
|
||||
if (inboundHandler != null) {
|
||||
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
|
||||
|
||||
@@ -26,8 +26,8 @@ import lombok.experimental.UtilityClass;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
+22
-23
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.bungee.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.netty.HandlerBoss;
|
||||
@@ -26,8 +25,8 @@ 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.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
|
||||
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@@ -49,42 +48,42 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
if (msg instanceof PacketWrapper) {
|
||||
final PacketWrapper packetWrapper = (PacketWrapper) msg;
|
||||
final DefinedPacket wrappedPacket = packetWrapper.packet;
|
||||
// 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;
|
||||
// We don't care about server pings; remove the handler
|
||||
if (handshake.getRequestedProtocol() == STATUS) {
|
||||
ctx.pipeline().remove(this);
|
||||
} else {
|
||||
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
|
||||
handleHandshake(ctx, handshake.getHost(), handshake.getProtocolVersion());
|
||||
}
|
||||
}
|
||||
// Intercept any server login packet by the client
|
||||
else if (wrappedPacket instanceof LoginRequest) {
|
||||
} else if (wrappedPacket instanceof LoginRequest) {
|
||||
// Make sure to use the potentially modified, real IP
|
||||
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
|
||||
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
|
||||
final ChannelWrapper channelWrapper;
|
||||
try {
|
||||
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
|
||||
} catch (final Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
final HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
|
||||
// This might sometimes happen if the client unexpectedly disconnects
|
||||
if (handlerBoss != null) {
|
||||
// Get the real IP address of the player through the channel wrapper
|
||||
final ChannelWrapper channelWrapper;
|
||||
try {
|
||||
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
|
||||
} catch (final Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.pipeline().remove(this);
|
||||
// Make sure to mark this packet as released
|
||||
packetWrapper.trySingleRelease();
|
||||
// Let Sonar process the login packet
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
handleLogin(ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
|
||||
return;
|
||||
}
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
// We've done our job - deject this pipeline
|
||||
ctx.pipeline().remove(this);
|
||||
// Make sure to mark this packet as released
|
||||
packetWrapper.trySingleRelease();
|
||||
// Let Sonar process the login packet
|
||||
handleLogin(channel, ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-73
@@ -49,11 +49,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
|
||||
/**
|
||||
* Validates and handles incoming handshake packets
|
||||
*
|
||||
* @param hostname Hostname (server address) sent by the client
|
||||
* @param protocol Protocol version number sent by the client
|
||||
*/
|
||||
protected final void handleHandshake(final @NotNull Channel channel,
|
||||
protected final void handleHandshake(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull String hostname,
|
||||
final int protocol) throws Exception {
|
||||
// Check if the hostname is invalid
|
||||
@@ -65,20 +62,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
protocolVersion = ProtocolVersion.fromId(protocol);
|
||||
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
ctx.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and handles incoming login packets
|
||||
*
|
||||
* @param channel Forwarded client channel
|
||||
* @param loginPacket Login packet sent by the client
|
||||
* @param username Username sent by the client
|
||||
* @param socketAddress Socket address of the client
|
||||
*/
|
||||
protected final void handleLogin(final @NotNull Channel channel,
|
||||
final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Runnable loginPacket,
|
||||
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Runnable initialLoginAction,
|
||||
final @NotNull String username,
|
||||
final @NotNull InetSocketAddress socketAddress) throws Exception {
|
||||
// Check if the player has already sent a login packet
|
||||
@@ -86,7 +77,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
}
|
||||
// Connections from unknown protocol versions will be discarded
|
||||
// as this is the safest way of handling unwanted connections
|
||||
// as this is the safest way of handling unwanted connections.
|
||||
// Sonar does not support snapshots or Minecraft versions older than 1.7.2
|
||||
if (protocolVersion.isUnknown()) {
|
||||
throw QuietDecoderException.INSTANCE;
|
||||
@@ -95,17 +86,17 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
this.username = username;
|
||||
|
||||
final InetAddress inetAddress = socketAddress.getAddress();
|
||||
channel.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
|
||||
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying);
|
||||
customDisconnect(ctx.channel(), alreadyVerifying, protocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
|
||||
customDisconnect(channel, protocolVersion, protocolBlacklisted);
|
||||
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,34 +106,34 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
if (limit > 0) {
|
||||
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
|
||||
if (score >= limit) {
|
||||
customDisconnect(channel, protocolVersion, blacklisted);
|
||||
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
initialLogin(ctx, inetAddress, loginPacket);
|
||||
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections if configured
|
||||
final boolean geyser = GeyserUtil.isGeyserConnection(channel, socketAddress);
|
||||
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
|
||||
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
|
||||
initialLogin(ctx, inetAddress, loginPacket);
|
||||
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we actually have to verify the player
|
||||
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
|
||||
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
|
||||
initialLogin(ctx, inetAddress, loginPacket);
|
||||
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast);
|
||||
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,10 +141,10 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
rewriteProtocol(ctx, channelRemovalListener);
|
||||
|
||||
// Queue the connection for further processing
|
||||
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, action) -> {
|
||||
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (action != null) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued);
|
||||
if (runnable != null) {
|
||||
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
|
||||
// Remove other instances of this IP address from the queue
|
||||
return null;
|
||||
}
|
||||
@@ -162,16 +153,41 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
// Check if the username matches the valid name regex to prevent
|
||||
// UTF-16 names or other types of exploits
|
||||
if (!Sonar.get().getConfig().getVerification().getValidNameRegex().matcher(username).matches()) {
|
||||
customDisconnect(channel, protocolVersion, invalidUsername);
|
||||
customDisconnect(ctx.channel(), invalidUsername, protocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance for the user and let the verification handler take over the channel
|
||||
new FallbackUserWrapper(channel, inetAddress, protocolVersion, username, fingerprint, geyser);
|
||||
new FallbackUserWrapper(ctx.channel(), inetAddress, protocolVersion, username, fingerprint, geyser);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the maximum accounts per IP limit check before letting the player join
|
||||
*/
|
||||
protected final void initialLogin(final @NotNull Channel channel,
|
||||
final @NotNull InetAddress inetAddress,
|
||||
final @NotNull Runnable loginPacket) throws Exception {
|
||||
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Skip the maximum online per IP check if it's disabled in the configuration
|
||||
if (count != null && maxOnlinePerIp > 0) {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
if (count >= maxOnlinePerIp) {
|
||||
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
|
||||
return count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the server know about the login packet
|
||||
loginPacket.run();
|
||||
// Increment the number of accounts with the same IP
|
||||
return count == null ? 1 : count + 1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all pipelines and rewrites them using our custom handlers
|
||||
*/
|
||||
@@ -196,58 +212,24 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the maximum accounts per IP limit check before letting the player join
|
||||
*
|
||||
* @param ctx Forwarded client channel context
|
||||
* @param inetAddress Resolved client IP address
|
||||
* @param loginPacket Login packet sent by the client
|
||||
*/
|
||||
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull InetAddress inetAddress,
|
||||
final @NotNull Runnable loginPacket) throws Exception {
|
||||
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Skip the maximum online per IP check if it's disabled in the configuration
|
||||
if (count != null && maxOnlinePerIp > 0) {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
if (count >= maxOnlinePerIp) {
|
||||
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
|
||||
return count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the server know about the login packet
|
||||
loginPacket.run();
|
||||
// Increment the number of accounts with the same IP
|
||||
return count == null ? 1 : count + 1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the player before verification (during login)
|
||||
* by replacing the encoder before running the method.
|
||||
*
|
||||
* @param packet Disconnect packet
|
||||
* @param channel Channel of the player
|
||||
* @param protocolVersion Protocol version of the player
|
||||
*/
|
||||
private void customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet) {
|
||||
private static void customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull FallbackPacket packet,
|
||||
final ProtocolVersion protocolVersion) {
|
||||
if (channel.isActive()) {
|
||||
if (channel.eventLoop().inEventLoop()) {
|
||||
_customDisconnect(channel, protocolVersion, packet);
|
||||
_customDisconnect(channel, packet, protocolVersion);
|
||||
} else {
|
||||
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet));
|
||||
channel.eventLoop().execute(() -> _customDisconnect(channel, packet, protocolVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull FallbackPacket packet) {
|
||||
/**
|
||||
* Disconnect the player before verification (during login)
|
||||
*/
|
||||
private static void _customDisconnect(final @NotNull Channel channel,
|
||||
final @NotNull FallbackPacket packet,
|
||||
final ProtocolVersion protocolVersion) {
|
||||
// Remove the connection handler pipeline to completely take over the channel
|
||||
final String handler = Sonar.get().getPlatform().getConnectionHandler();
|
||||
if (channel.pipeline().context(handler) != null) {
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
package xyz.jonesdev.sonar.common.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
@@ -50,8 +50,8 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
|
||||
@Getter
|
||||
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
|
||||
public final class FallbackUserWrapper implements FallbackUser {
|
||||
@Accessors(fluent = true)
|
||||
private final Channel channel;
|
||||
private final ChannelPipeline pipeline;
|
||||
private final InetAddress inetAddress;
|
||||
private final ProtocolVersion protocolVersion;
|
||||
private final String fingerprint;
|
||||
@@ -68,7 +68,6 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
final @NotNull String fingerprint,
|
||||
final boolean geyser) {
|
||||
this.channel = channel;
|
||||
this.pipeline = channel.pipeline();
|
||||
this.inetAddress = inetAddress;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.username = username;
|
||||
@@ -103,8 +102,8 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
|
||||
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
|
||||
channel.pipeline().addLast(FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
write(loginSuccess);
|
||||
@@ -114,19 +113,19 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
|
||||
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
|
||||
pipeline.addLast(FALLBACK_PACKET_DECODER, newDecoder);
|
||||
channel.pipeline().addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
|
||||
channel.pipeline().addLast(FALLBACK_PACKET_DECODER, newDecoder);
|
||||
// Listen for all incoming packets by setting the packet listener
|
||||
newDecoder.setListener(new FallbackPreJoinHandler(this));
|
||||
|
||||
// Make sure to catch all exceptions during the verification
|
||||
pipeline.addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
|
||||
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
final FallbackPacketEncoder encoder = pipeline.get(FallbackPacketEncoder.class);
|
||||
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
|
||||
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
|
||||
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
|
||||
}
|
||||
|
||||
+1
-1
@@ -24,9 +24,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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;
|
||||
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
+19
-11
@@ -44,9 +44,8 @@ import java.util.UUID;
|
||||
public class FallbackPreparer {
|
||||
private final Random RANDOM = new Random();
|
||||
|
||||
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt(50);
|
||||
public static final int VEHICLE_BOAT_ENTITY_ID = PLAYER_ENTITY_ID + 1 + RANDOM.nextInt(10);
|
||||
public static final int VEHICLE_MINECART_ENTITY_ID = VEHICLE_BOAT_ENTITY_ID + 1 + RANDOM.nextInt(10);
|
||||
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
|
||||
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
|
||||
|
||||
public static final CompoundBinaryTag MAP_ITEM_NBT = CompoundBinaryTag.builder()
|
||||
.put("map", IntBinaryTag.intBinaryTag(0)) // map type
|
||||
@@ -80,6 +79,7 @@ public class FallbackPreparer {
|
||||
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlivePacket(RANDOM.nextInt());
|
||||
|
||||
public static FallbackPacket loginSuccess;
|
||||
public FallbackPacket welcomeMessage;
|
||||
public FallbackPacket enterCodeMessage;
|
||||
public FallbackPacket incorrectCaptcha;
|
||||
public static FallbackPacket joinGame;
|
||||
@@ -99,8 +99,7 @@ public class FallbackPreparer {
|
||||
public FallbackPacket teleportMinecart;
|
||||
public FallbackPacket spawnBoatEntity;
|
||||
public FallbackPacket spawnMinecartEntity;
|
||||
public FallbackPacket setBoatPassengers;
|
||||
public FallbackPacket setMinecartPassengers;
|
||||
public FallbackPacket setPassengers;
|
||||
public FallbackPacket[] xpCountdown;
|
||||
|
||||
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
|
||||
@@ -181,17 +180,26 @@ public class FallbackPreparer {
|
||||
}
|
||||
|
||||
// Prepare packets for the vehicle check
|
||||
removeBoat = new RemoveEntitiesPacket(VEHICLE_BOAT_ENTITY_ID);
|
||||
removeBoat = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
|
||||
teleportMinecart = new TeleportEntityPacket(
|
||||
VEHICLE_MINECART_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
|
||||
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_BOAT_ENTITY_ID,
|
||||
VEHICLE_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
|
||||
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
|
||||
EntityType.BOAT, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
|
||||
0, 0, 0, 0));
|
||||
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_MINECART_ENTITY_ID,
|
||||
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
|
||||
EntityType.MINECART, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
|
||||
0, 0, 0, 0));
|
||||
setBoatPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_BOAT_ENTITY_ID, PLAYER_ENTITY_ID));
|
||||
setMinecartPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_MINECART_ENTITY_ID, PLAYER_ENTITY_ID));
|
||||
setPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
|
||||
|
||||
// If the welcome message is empty, we don't need to send a message to the player
|
||||
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
|
||||
if (welcome.isEmpty()) {
|
||||
welcomeMessage = null;
|
||||
} else {
|
||||
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
|
||||
MiniMessage.miniMessage().deserialize(welcome,
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
|
||||
}
|
||||
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|
||||
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
|
||||
|
||||
+10
-15
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
|
||||
import java.util.function.Function;
|
||||
@@ -27,6 +26,7 @@ import java.util.function.Function;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
|
||||
// TODO: load mappings from a separate file
|
||||
@Getter
|
||||
@SuppressWarnings("unused")
|
||||
@RequiredArgsConstructor
|
||||
public enum BlockType {
|
||||
@@ -67,7 +67,7 @@ public enum BlockType {
|
||||
return 7385;
|
||||
}
|
||||
return 7389;
|
||||
}, 0.75f),
|
||||
}, protocolVersion -> (double) 0.75f),
|
||||
TRAPDOOR(protocolVersion -> {
|
||||
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_7_6) <= 0) {
|
||||
@@ -107,7 +107,7 @@ public enum BlockType {
|
||||
return 10273;
|
||||
}
|
||||
return 10414;
|
||||
}, 0.1875),
|
||||
}, protocolVersion -> 0.1875),
|
||||
END_PORTAL_FRAME(protocolVersion -> {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
|
||||
return 120;
|
||||
@@ -140,7 +140,7 @@ public enum BlockType {
|
||||
return 7410;
|
||||
}
|
||||
return 7414;
|
||||
}, 0.8125),
|
||||
}, protocolVersion -> 0.8125),
|
||||
DAYLIGHT_SENSOR(protocolVersion -> {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
|
||||
return 151;
|
||||
@@ -176,7 +176,7 @@ public enum BlockType {
|
||||
return 9067;
|
||||
}
|
||||
return 9207;
|
||||
}, 0.375),
|
||||
}, protocolVersion -> 0.375),
|
||||
COBBLESTONE_WALL(protocolVersion -> {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
|
||||
return 139;
|
||||
@@ -209,7 +209,7 @@ public enum BlockType {
|
||||
return 7918;
|
||||
}
|
||||
return 7922;
|
||||
}, 1.5),
|
||||
}, protocolVersion -> 1.5),
|
||||
STONE_SLABS(protocolVersion -> {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
|
||||
return 44;
|
||||
@@ -245,7 +245,7 @@ public enum BlockType {
|
||||
return 11090;
|
||||
}
|
||||
return 11231;
|
||||
}, 0.5),
|
||||
}, protocolVersion -> 0.5),
|
||||
WHITE_CARPET(protocolVersion -> {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
|
||||
return 171;
|
||||
@@ -281,13 +281,8 @@ public enum BlockType {
|
||||
return 10587;
|
||||
}
|
||||
return 10728;
|
||||
}, 0.0625);
|
||||
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
|
||||
|
||||
private final Function<ProtocolVersion, Integer> idFunction;
|
||||
@Getter
|
||||
private final double blockHeight;
|
||||
|
||||
public int getId(final @NotNull ProtocolVersion protocolVersion) {
|
||||
return idFunction.apply(protocolVersion);
|
||||
}
|
||||
private final Function<ProtocolVersion, Integer> id;
|
||||
private final Function<ProtocolVersion, Double> blockHeight;
|
||||
}
|
||||
+2
-2
@@ -54,7 +54,7 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
|
||||
|
||||
for (final BlockUpdate block : blockUpdates) {
|
||||
byteBuf.writeShort(block.getLegacyBlockState());
|
||||
final int blockId = block.getBlockType().getId(protocolVersion);
|
||||
final int blockId = block.getBlockType().getId().apply(protocolVersion);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
|
||||
writeVarInt(byteBuf, blockId);
|
||||
} else {
|
||||
@@ -82,7 +82,7 @@ public final class UpdateSectionBlocksPacket implements FallbackPacket {
|
||||
|
||||
for (final BlockUpdate block : blockUpdates) {
|
||||
// https://wiki.vg/Protocol#Update_Section_Blocks
|
||||
final int shiftedBlockId = block.getBlockType().getId(protocolVersion) << 12;
|
||||
final int shiftedBlockId = block.getBlockType().getId().apply(protocolVersion) << 12;
|
||||
final long positionIdValue = shiftedBlockId | block.getBlockState();
|
||||
writeVarLong(byteBuf, positionIdValue);
|
||||
}
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public final class FallbackCaptchaHandler extends FallbackVerificationHandler {
|
||||
// Make sure the player knows that they have to enter the code in chat
|
||||
user.delayedWrite(enterCodeMessage);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
user.channel().flush();
|
||||
}
|
||||
|
||||
private final String answer;
|
||||
|
||||
+8
-3
@@ -70,11 +70,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
|
||||
// Spawn the invisible platform below the player
|
||||
if (enableCollisionsCheck) {
|
||||
final int index = RANDOM.nextInt(BLOCKS_PACKETS.length);
|
||||
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight();
|
||||
blockHeight = POSSIBLE_BLOCK_TYPES[index].getBlockHeight().apply(user.getProtocolVersion());
|
||||
user.delayedWrite(BLOCKS_PACKETS[index]);
|
||||
}
|
||||
// Send all packets at once
|
||||
user.getChannel().flush();
|
||||
user.channel().flush();
|
||||
|
||||
// 1.8 and below don't have TeleportConfirm packets, which is why we're skipping that check.
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
|
||||
@@ -97,7 +97,7 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
|
||||
preJoinHandler.validateClientInformation();
|
||||
}
|
||||
// Send the player to the next verification handler
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.channel().pipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(new FallbackProtocolHandler(user));
|
||||
}
|
||||
|
||||
@@ -157,6 +157,11 @@ public final class FallbackGravityHandler extends FallbackVerificationHandler {
|
||||
// Synchronize the Y coordinate
|
||||
this.y = dynamicSpawnYPosition;
|
||||
checkMovement = true;
|
||||
|
||||
// Send the welcome message to the player
|
||||
if (welcomeMessage != null) {
|
||||
user.write(welcomeMessage);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -49,7 +49,7 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
|
||||
// This trick helps in reducing unnecessary outgoing server traffic
|
||||
// by avoiding sending other packets to clients that are potentially bots.
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
user.getChannel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
|
||||
user.channel().eventLoop().schedule(this::markSuccess, 100L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
sendKeepAlive();
|
||||
}
|
||||
@@ -78,12 +78,12 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
|
||||
// Write the FinishConfiguration packet to the buffer
|
||||
user.delayedWrite(FINISH_CONFIGURATION);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
user.channel().flush();
|
||||
}
|
||||
|
||||
private void markSuccess() {
|
||||
// Pass the player to the next verification handler
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.channel().pipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(new FallbackGravityHandler(user, this));
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ public final class FallbackPreJoinHandler extends FallbackVerificationHandler {
|
||||
|
||||
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
|
||||
// Update the packet registry state in the encoder and decoder pipelines
|
||||
user.getPipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
|
||||
user.getPipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
|
||||
user.channel().pipeline().get(FallbackPacketDecoder.class).updateRegistry(registry);
|
||||
user.channel().pipeline().get(FallbackPacketEncoder.class).updateRegistry(registry);
|
||||
}
|
||||
|
||||
private void synchronizeClientRegistry() {
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
|
||||
final SetHeldItemPacket heldItemPacket = new SetHeldItemPacket(expectedSlotId);
|
||||
user.delayedWrite(heldItemPacket);
|
||||
user.delayedWrite(heldItemPacket);
|
||||
user.getChannel().flush();
|
||||
user.channel().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public final class FallbackProtocolHandler extends FallbackVerificationHandler {
|
||||
private void markSuccess() {
|
||||
// Either send the player to the vehicle check,
|
||||
// send the player to the CAPTCHA, or finish the verification.
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.channel().pipeline().get(FallbackPacketDecoder.class);
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
|
||||
decoder.setListener(new FallbackVehicleHandler(user));
|
||||
|
||||
+6
-6
@@ -34,8 +34,8 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
|
||||
|
||||
// Send the necessary packets to mount the player on the boat vehicle
|
||||
user.delayedWrite(spawnBoatEntity);
|
||||
user.delayedWrite(setBoatPassengers);
|
||||
user.getChannel().flush();
|
||||
user.delayedWrite(setPassengers);
|
||||
user.channel().flush();
|
||||
}
|
||||
|
||||
private int expectedTransactionId;
|
||||
@@ -47,7 +47,7 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
|
||||
// Pass the player to the next best verification handler
|
||||
if (user.isForceCaptcha() || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
// Either send the player to the CAPTCHA or finish the verification.
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.channel().pipeline().get(FallbackPacketDecoder.class);
|
||||
// Send the player to the CAPTCHA handler
|
||||
decoder.setListener(new FallbackCaptchaHandler(user));
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
|
||||
waitingRemoveMinecart = true;
|
||||
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
|
||||
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
|
||||
user.getChannel().flush();
|
||||
user.channel().flush();
|
||||
} else {
|
||||
// If the player is riding a boat, remove the entity
|
||||
// The next Y coordinate the player will send is going
|
||||
@@ -113,8 +113,8 @@ public final class FallbackVehicleHandler extends FallbackVerificationHandler {
|
||||
expectedTransactionId = (short) -RANDOM.nextInt(Short.MAX_VALUE);
|
||||
user.delayedWrite(new TransactionPacket(0, expectedTransactionId, false));
|
||||
user.delayedWrite(spawnMinecartEntity);
|
||||
user.delayedWrite(setMinecartPassengers);
|
||||
user.getChannel().flush();
|
||||
user.delayedWrite(setPassengers);
|
||||
user.channel().flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-5
@@ -63,12 +63,12 @@ public abstract class FallbackVerificationHandler implements FallbackPacketListe
|
||||
if (user.isGeyser()) {
|
||||
user.write(transferToOrigin);
|
||||
// Make sure we cannot receive any more packets from the player
|
||||
user.getPipeline().remove(FallbackPacketDecoder.class);
|
||||
user.getPipeline().remove(FallbackPacketEncoder.class);
|
||||
user.getPipeline().remove(FallbackVarInt21FrameDecoder.class);
|
||||
user.getPipeline().remove(FallbackVarIntLengthEncoder.class);
|
||||
user.channel().pipeline().remove(FallbackPacketDecoder.class);
|
||||
user.channel().pipeline().remove(FallbackPacketEncoder.class);
|
||||
user.channel().pipeline().remove(FallbackVarInt21FrameDecoder.class);
|
||||
user.channel().pipeline().remove(FallbackVarIntLengthEncoder.class);
|
||||
} else {
|
||||
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
|
||||
closeWith(user.channel(), user.getProtocolVersion(), transferToOrigin);
|
||||
}
|
||||
} else {
|
||||
// Disconnect player with the verification success message
|
||||
|
||||
@@ -39,12 +39,12 @@ public final class ComponentHolder {
|
||||
private final String serializedComponent;
|
||||
private BinaryTag cachedBinaryTag;
|
||||
|
||||
public ComponentHolder(final Component component) {
|
||||
public ComponentHolder(final @NotNull Component component) {
|
||||
this.serializedComponent = GsonComponentSerializer.gson().serialize(component);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
private BinaryTag serialize(final @NotNull JsonElement json) {
|
||||
if (json instanceof JsonPrimitive) {
|
||||
final JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
|
||||
|
||||
@@ -141,8 +141,7 @@ public final class ComponentHolder {
|
||||
return EndBinaryTag.endBinaryTag();
|
||||
}
|
||||
|
||||
public void write(final @NotNull ByteBuf byteBuf,
|
||||
final @NotNull ProtocolVersion protocolVersion) {
|
||||
public void write(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20_3) >= 0) {
|
||||
if (cachedBinaryTag == null) {
|
||||
cachedBinaryTag = serialize(new JsonParser().parse(serializedComponent));
|
||||
|
||||
@@ -105,20 +105,20 @@ public class ProtocolUtil {
|
||||
throw DEBUG ? new DecoderException("Bad VarInt") : QuietDecoderException.INSTANCE;
|
||||
}
|
||||
|
||||
public static void writeVarInt(final ByteBuf byteBuf, final int value) {
|
||||
public static void writeVarInt(final @NotNull ByteBuf byteBuf, final int value) {
|
||||
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
|
||||
// that the proxy will write, to improve inlining.
|
||||
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||
byteBuf.writeByte(value);
|
||||
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
|
||||
final int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
|
||||
byteBuf.writeShort(w);
|
||||
} else {
|
||||
writeVarIntFull(byteBuf, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeVarIntFull(final ByteBuf byteBuf, final int value) {
|
||||
private void writeVarIntFull(final @NotNull ByteBuf byteBuf, final int value) {
|
||||
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
|
||||
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||
byteBuf.writeByte(value);
|
||||
@@ -140,7 +140,7 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeVarLong(final ByteBuf byteBuf, final long value) {
|
||||
public static void writeVarLong(final @NotNull ByteBuf byteBuf, final long value) {
|
||||
// Peel the one and two byte count cases explicitly as they are the most common VarLong sizes
|
||||
// that the proxy will write, to improve inlining.
|
||||
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
|
||||
@@ -153,7 +153,7 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeVarLongFull(final ByteBuf byteBuf, final long value) {
|
||||
private void writeVarLongFull(final @NotNull ByteBuf byteBuf, final long value) {
|
||||
if ((value & 0xFFFFFFFFFFFFFF80L) == 0L) {
|
||||
byteBuf.writeByte((byte) value);
|
||||
} else if ((value & 0xFFFFFFFFFFFFC000L) == 0L) {
|
||||
@@ -224,7 +224,7 @@ public class ProtocolUtil {
|
||||
|
||||
public static void closeWith(final @NotNull Channel channel,
|
||||
final @NotNull ProtocolVersion protocolVersion,
|
||||
final @NotNull Object msg) {
|
||||
final Object msg) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_7_2) >= 0) {
|
||||
channel.eventLoop().execute(() -> {
|
||||
@@ -242,11 +242,11 @@ public class ProtocolUtil {
|
||||
return new UUID(byteBuf.readLong(), byteBuf.readLong());
|
||||
}
|
||||
|
||||
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf) {
|
||||
public static byte @NotNull [] readByteArray(final @NotNull ByteBuf byteBuf) {
|
||||
return readByteArray(byteBuf, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static byte @NotNull [] readByteArray(final ByteBuf byteBuf, final int cap) {
|
||||
public static byte @NotNull [] readByteArray(final @NotNull ByteBuf byteBuf, final int cap) {
|
||||
final int length = readVarInt(byteBuf);
|
||||
checkState(length >= 0, "Got a negative-length array");
|
||||
checkState(length <= cap, "Bad array size");
|
||||
@@ -256,7 +256,7 @@ public class ProtocolUtil {
|
||||
return array;
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf byteBuf, final int cap) throws DecoderException {
|
||||
public static @NotNull String readString(final @NotNull ByteBuf byteBuf, final int cap) throws DecoderException {
|
||||
final int length = readVarInt(byteBuf);
|
||||
return readString(byteBuf, cap, length);
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public class ProtocolUtil {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void writeString(final ByteBuf byteBuf, final @NotNull CharSequence str) {
|
||||
public static void writeString(final @NotNull ByteBuf byteBuf, final @NotNull CharSequence str) {
|
||||
final int size = ByteBufUtil.utf8Bytes(str);
|
||||
writeVarInt(byteBuf, size);
|
||||
byteBuf.writeCharSequence(str, StandardCharsets.UTF_8);
|
||||
@@ -291,13 +291,13 @@ public class ProtocolUtil {
|
||||
byteBuf.writeInt((int) uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static void writeByteArray(final ByteBuf byteBuf, final byte @NotNull [] bytes) {
|
||||
public static void writeByteArray(final @NotNull ByteBuf byteBuf, final byte @NotNull [] bytes) {
|
||||
checkState(bytes.length < Short.MAX_VALUE, "Too long array");
|
||||
writeVarInt(byteBuf, bytes.length);
|
||||
byteBuf.writeBytes(bytes);
|
||||
}
|
||||
|
||||
public static void writeStringArray(final ByteBuf byteBuf, final String @NotNull [] stringArray) {
|
||||
public static void writeStringArray(final @NotNull ByteBuf byteBuf, final String @NotNull [] stringArray) {
|
||||
writeVarInt(byteBuf, stringArray.length);
|
||||
for (final String s : stringArray) {
|
||||
writeString(byteBuf, s);
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.exception;
|
||||
package xyz.jonesdev.sonar.common.util.exception;
|
||||
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Zpráva zobrazená hráči, když zadá špatnou odpověď do chatu
|
||||
incorrect: '<prefix><red>Zadali jste špatnou odpověď! Zkuste to prosím znovu.'
|
||||
|
||||
# Zpráva zobrazená hráči při ověřování
|
||||
welcome: '<prefix><green>Jste ověřováni. Prosím, nehýbejte se; jedná se o automatický proces.'
|
||||
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, zatímco captcha ještě není připravena
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -199,6 +199,9 @@ verification:
|
||||
# Nachricht, die dem Spieler angezeigt wird, wenn er den falschen Code im Chat eingibt
|
||||
incorrect: '<prefix><red>Du hast eine falsche Antwort eingegeben! Bitte versuche es erneut.'
|
||||
|
||||
# Nachricht, die dem Spieler angezeigt wird, wenn er überprüft wird
|
||||
welcome: '<prefix><green>Sie werden gerade überprüft. Bitte bewegen Sie sich nicht, dies ist ein automatischer Vorgang.'
|
||||
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand beitritt, während der CAPTCHA noch nicht vorbereitet wurde
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>You have entered a wrong answer! Please try again.'
|
||||
|
||||
# Message shown to the player when they are being verified
|
||||
welcome: '<prefix><green>You are being verified. Please do not move; this is an automatic process.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>¡Has introducido una respuesta incorrecta! Por favor, inténtalo de nuevo.'
|
||||
|
||||
# Mensaje que se muestra al jugador cuando está siendo verificado
|
||||
welcome: '<prefijo><verde>Estás siendo verificado. Por favor, no te muevas; se trata de un proceso automático.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Message affiché au joueur lorsqu'il entre la mauvaise réponse dans le chat
|
||||
incorrect: '<prefix><red>Vous avez saisi une mauvaise réponse ! Veuillez réessayer.'
|
||||
|
||||
# Message affiché au joueur lorsqu'il est en cours de vérification
|
||||
welcome: '<prefix><green>Vous êtes en cours de vérification. Ne bougez pas, il s''agit d''un processus automatique.'
|
||||
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint alors que le CAPTCHA n'a pas été préparé
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Pesan yang ditampilkan kepada pemain saat mereka memasukkan jawaban yang salah di chat
|
||||
incorrect: '<prefix><red>Anda telah memasukkan jawaban yang salah! Silakan coba lagi.'
|
||||
|
||||
# Pesan yang ditampilkan kepada pemain ketika mereka sedang diverifikasi
|
||||
welcome: '<prefix><green>Anda sedang diverifikasi. Jangan bergerak; ini adalah proses otomatis.'
|
||||
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung sementara captcha belum disiapkan
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა ისინი ჩაწერენ არასწორ პასუხს ჩატში
|
||||
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. გთხოვთ ხელახლა ცადოთ.'
|
||||
|
||||
# Message shown to the player when they are being verified
|
||||
welcome: '<prefix><green>You are being verified. Please do not move; this is an automatic process.'
|
||||
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა მაგრამ CAPTCHA არ არის მზად
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Bericht dat aan de speler wordt getoond wanneer hij of zij het verkeerde antwoord invoert in de chat
|
||||
incorrect: '<prefix><red>Je hebt een verkeerd antwoord ingevoerd! Probeer het opnieuw.'
|
||||
|
||||
# Bericht dat aan de speler wordt getoond als hij wordt geverifieerd
|
||||
welcome: '<prefix><green>U wordt geverifieerd. Beweeg je niet, dit is een automatisch proces.'
|
||||
|
||||
# Verbindingsbericht dat wordt getoond als iemand zich aansluit terwijl de captcha nog niet is voorbereid
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# Placeholder for every '<prefix>' in this configuration file
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Placeholder for every '<support-url>' in this configuration file
|
||||
support-url: jhttps://jonesdev.xyz/discord/
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Placeholder for every '<header>' in this configuration file
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
@@ -201,6 +201,9 @@ verification:
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>Wprowadzono nieprawidłowy kod. Spróbuj ponownie.'
|
||||
|
||||
# Wiadomość wyświetlana graczowi podczas weryfikacji
|
||||
welcome: '<prefix><green>Jesteś w trakcie weryfikacji. Nie ruszaj się, to proces automatyczny.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Сообщение, которое показывается игроку, когда он вводит неправильную капчу в чат
|
||||
incorrect: '<prefix><red>Вы ввели неправильный ответ! Пожалуйста, попробуйте еще раз.'
|
||||
|
||||
# Сообщение, показываемое игроку при прохождении верификации
|
||||
welcome: '<prefix><green>Вы проходите верификацию. Пожалуйста, не двигайтесь, это автоматический процесс.'
|
||||
|
||||
# Сообщение, которое показывается когда капча еще не подготовлена
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# Message that is shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>Yanlış bir cevap girdiniz! Lütfen tekrar deneyin.'
|
||||
|
||||
# Doğrulandıklarında oyuncuya gösterilen mesaj
|
||||
welcome: '<prefix><green>Doğrulanıyorsunuz. Lütfen hareket etmeyin; bu otomatik bir süreçtir.'
|
||||
|
||||
# CAPTCHA henüz hazırlanmamışken birisi katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
@@ -198,6 +198,9 @@ verification:
|
||||
# 当玩家输入错误的验证码时显示的消息
|
||||
incorrect: '<prefix><red>您输入了错误的答案! 请重试.'
|
||||
|
||||
# 当玩家正在接受验证时显示的信息
|
||||
welcome: '<prefix><green>您正在接受验证. 请不要移动; 这是一个自动过程.'
|
||||
|
||||
# 当验证码还在准备的时候显示的踢出消息
|
||||
currently-preparing:
|
||||
- <header>
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev
|
||||
version=2.1.8
|
||||
version=2.1.9
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -2,13 +2,13 @@
|
||||
netty = "4.1.113.Final"
|
||||
adventure = "4.17.0"
|
||||
adventure-platform = "4.3.4"
|
||||
bstats = "3.0.3"
|
||||
bstats = "3.1.0"
|
||||
libby = "2.0.0-SNAPSHOT"
|
||||
pluginyml = "0.6.0"
|
||||
|
||||
[plugins]
|
||||
indra-git = "net.kyori.indra.git:3.1.3"
|
||||
shadow = "com.gradleup.shadow:8.3.1"
|
||||
shadow = "com.gradleup.shadow:8.3.2"
|
||||
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
|
||||
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
|
||||
|
||||
|
||||
+6
-7
@@ -26,27 +26,26 @@ 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 {
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
|
||||
if (msg instanceof HandshakePacket handshake) {
|
||||
// We don't care about server pings; remove the handler
|
||||
if (handshake.getNextStatus() == STATUS) {
|
||||
ctx.pipeline().remove(this);
|
||||
} else {
|
||||
handleHandshake(ctx.channel(), handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
|
||||
handleHandshake(ctx, handshake.getServerAddress(), handshake.getProtocolVersion().getProtocol());
|
||||
}
|
||||
} else if (msg instanceof ServerLoginPacket serverLogin) {
|
||||
// Make sure to use the potentially modified, original IP
|
||||
final MinecraftConnection minecraftConnection = (MinecraftConnection) ctx.pipeline().get(HANDLER);
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
|
||||
// Deject this pipeline and let Sonar process the login packet
|
||||
ctx.pipeline().remove(this);
|
||||
handleLogin(ctx.channel(), ctx, () -> ctx.fireChannelRead(msg), serverLogin.getUsername(), socketAddress);
|
||||
// Make sure to use the potentially modified, original IP
|
||||
final MinecraftConnection minecraftConnection = ctx.pipeline().get(MinecraftConnection.class);
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) minecraftConnection.getRemoteAddress();
|
||||
handleLogin(ctx, () -> ctx.fireChannelRead(msg), serverLogin.getUsername(), socketAddress);
|
||||
return;
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
|
||||
+1
-1
@@ -23,8 +23,8 @@ 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.exception.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
|
||||
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
Reference in new issue
Block a user