Compare commits

...
11 Commits
Author SHA1 Message Date
Jones 0c8106eaf6 Release 2.1.9 (#389) 2024-09-29 13:32:05 +02:00
Jones 92ac5fbeb2 Fix block heights varying on different versions 2024-09-29 13:26:09 +02:00
Jones b800a2d154 Use one entity ID for all vehicles 2024-09-28 12:21:44 +02:00
Jones 1957463b81 Fix the Discord link in Polish translations 2024-09-28 11:59:09 +02:00
Jones 25114eabe5 Add verification welcome message to config 2024-09-28 11:58:54 +02:00
Jones a45b526ffb Clean up and improve the API 2024-09-25 17:52:35 +02:00
Jones 7b70d32e84 Don't store the pipeline separately in user 2024-09-25 17:48:51 +02:00
Jones e84c1a54db Small micro-optimizations to networking 2024-09-25 17:42:51 +02:00
Jones fd869ac199 Fix potential NullPointerException 2024-09-25 17:23:00 +02:00
dependabot[bot] abf1479a84 Bump com.gradleup.shadow from 8.3.1 to 8.3.2 (#387)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.1...8.3.2)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:49 +02:00
dependabot[bot] c00392e062 Bump bstats from 3.0.3 to 3.1.0 (#386)
Bumps `bstats` from 3.0.3 to 3.1.0.

Updates `org.bstats:bstats-bungeecord` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-bukkit` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-velocity` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-velocity
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:32 +02:00
38 changed files with 227 additions and 245 deletions

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);
}
@@ -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;
@@ -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;
}
}
}
@@ -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));
}
@@ -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;
@@ -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()) {
@@ -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;
}
@@ -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);
}
@@ -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;
@@ -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;
}
@@ -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() {
@@ -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));
@@ -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
@@ -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);
@@ -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
View File
@@ -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 -2
View File
@@ -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" }
@@ -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);
@@ -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;