diff --git a/common/build.gradle.kts b/common/build.gradle.kts index de358e8e..4eff3ab9 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { compileOnly(libs.annotations) compileOnly(libs.mojang.authlib) compileOnly(libs.mojang.brigadier) + compileOnly(libs.gson) compileOnly(libs.guava) compileOnly(libs.jspecify) compileOnly(libs.mixin) diff --git a/common/src/main/java/org/adde0109/pcf/PCF.java b/common/src/main/java/org/adde0109/pcf/PCF.java index 24bd9d7d..dbfeb600 100644 --- a/common/src/main/java/org/adde0109/pcf/PCF.java +++ b/common/src/main/java/org/adde0109/pcf/PCF.java @@ -2,6 +2,8 @@ import static dev.neuralnexus.taterapi.network.Protocol.map; +import static org.adde0109.pcf.forwarding.PreLoginHandler.HANDLERS; + import dev.neuralnexus.taterapi.loader.EntrypointLoader; import dev.neuralnexus.taterapi.meta.Constraint; import dev.neuralnexus.taterapi.meta.Constraints; @@ -15,11 +17,12 @@ import dev.neuralnexus.taterapi.network.PayloadRegistry; import org.adde0109.pcf.forwarding.Mode; +import org.adde0109.pcf.forwarding.PreLoginHandler; +import org.adde0109.pcf.forwarding.bungeeguard.BungeeGuard; import org.adde0109.pcf.forwarding.compatibility.prelogin.ArclightPreLogin; import org.adde0109.pcf.forwarding.compatibility.prelogin.MohistPreLogin; import org.adde0109.pcf.forwarding.compatibility.prelogin.SpigotPreLogin; import org.adde0109.pcf.forwarding.compatibility.prelogin.SpongePreLogin; -import org.adde0109.pcf.forwarding.modern.ModernForwarding; import org.adde0109.pcf.forwarding.modern.PlayerInfoQueryPayload; import org.adde0109.pcf.forwarding.modern.VelocityProxy; import org.jetbrains.annotations.ApiStatus; @@ -78,38 +81,30 @@ void onInit() { } loader.onInit(); - // Modern forwarding init - if (this.forwarding().enabled() && this.forwarding().mode().equals(Mode.MODERN)) { - PayloadRegistry.register( - PlayerInfoQueryPayload.TYPE, - map(PlayerInfoQueryPayload.IDENTIFIER, MinecraftVersions.V7_2)); - + // Forwarding init + if (this.forwarding().enabled()) { if (Constraint.builder().platform(Platforms.ARCLIGHT).result()) { - logger.debug("Arclight detected, applying pre-login post processor"); + logger.debug("Arclight detected, applying pre-login handler"); if (Constraint.range(MinecraftVersions.V14, MinecraftVersions.V20_1).result()) { - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> { + HANDLERS.add( + (slpl, profile, _) -> { slpl.bridge$setGameProfile(profile); ArclightPreLogin.V14.preLogin(slpl); }); } else if (Constraint.builder().version(MinecraftVersions.V20_2).result()) { - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> ArclightPreLogin.V20_2.preLogin(slpl, profile)); + HANDLERS.add( + (slpl, profile, _) -> ArclightPreLogin.V20_2.preLogin(slpl, profile)); } else if (Constraint.noLessThan(MinecraftVersions.V20_3).result()) { - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> ArclightPreLogin.V20_4.preLogin(slpl, profile)); + HANDLERS.add( + (slpl, profile, _) -> ArclightPreLogin.V20_4.preLogin(slpl, profile)); } } else if (Constraint.builder() .platform(Platforms.MOHIST) .version(MinecraftVersions.V20_1) .result()) { - logger.debug("Mohist detected, applying pre-login post processor"); - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> { + logger.debug("Mohist detected, applying pre-login handler"); + HANDLERS.add( + (slpl, profile, _) -> { slpl.bridge$setGameProfile(profile); MohistPreLogin.V20_1.fireEvents(slpl); }); @@ -117,10 +112,9 @@ void onInit() { .platform(Platforms.YOUER) .version(MinecraftVersions.V21_1) .result()) { - logger.debug("Youer detected, applying pre-login post processor"); - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> { + logger.debug("Youer detected, applying pre-login handler"); + HANDLERS.add( + (slpl, profile, _) -> { MohistPreLogin.Youer.fireEvents(slpl, profile); slpl.bridge$startClientVerification(profile); }); @@ -135,10 +129,9 @@ void onInit() { .platform(Platforms.MAGMA, Platforms.KETTING) .version(MinecraftVersions.V20_1)) .result()) { - logger.debug("Forge+Bukkit hybrid detected, applying pre-login post processor"); - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> { + logger.debug("Forge+Bukkit hybrid detected, applying pre-login handler"); + HANDLERS.add( + (slpl, profile, _) -> { slpl.bridge$setGameProfile(profile); SpigotPreLogin.Legacy.fireEvents(slpl); }); @@ -150,11 +143,8 @@ void onInit() { .platform(Platforms.MOHIST) .version(MinecraftVersions.V20_2)) .result()) { - logger.debug( - "[Neo]Forge+Bukkit hybrid detected, applying pre-login post processor"); - ModernForwarding.postProcessors.removeFirst(); - ModernForwarding.postProcessors.add( - (slpl, profile, c) -> SpigotPreLogin.V20_2.fireEvents(slpl, profile)); + logger.debug("[Neo]Forge+Bukkit hybrid detected, applying pre-login handler"); + HANDLERS.add((slpl, profile, _) -> SpigotPreLogin.V20_2.fireEvents(slpl, profile)); } else if (Constraints.builder() .or( Constraint.builder() @@ -169,18 +159,23 @@ void onInit() { .platform(Platforms.NEOTENET) .version(MinecraftVersions.V21_1, MinecraftVersions.V21_10)) .result()) { - logger.debug( - "[Neo]Forge+Bukkit hybrid detected, applying pre-login post processor"); - ModernForwarding.postProcessors.addFirst( - (slpl, profile, c) -> + logger.debug("[Neo]Forge+Bukkit hybrid detected, applying pre-login handler"); + HANDLERS.add( + (slpl, profile, _) -> SpigotPreLogin.V20_5.callPlayerPreLoginEvents(slpl, profile)); + HANDLERS.add(PreLoginHandler.DEFAULT_HANDLER); + } + + // Serves both as a fallback and to populate the default + if (HANDLERS.isEmpty()) { + HANDLERS.add(PreLoginHandler.DEFAULT_HANDLER); } if (Constraint.range(MinecraftVersions.V16, MinecraftVersions.V18_2) .platform(Platforms.SPONGE) .result()) { - logger.debug("SpongeAPI 8 or 9 detected, applying pre-login post processor"); - ModernForwarding.postProcessors.addFirst( + logger.debug("SpongeAPI 8 or 9 detected, applying pre-login handler"); + HANDLERS.addFirst( (slpl, profile, c) -> { slpl.bridge$setGameProfile(profile); c.setCancelled(SpongePreLogin.API8.fireAuthEvent(slpl)); @@ -188,6 +183,19 @@ void onInit() { } } + // Modern forwarding init + if (this.forwarding().enabled() && this.forwarding().mode().isModern()) { + PayloadRegistry.register( + PlayerInfoQueryPayload.TYPE, + map(PlayerInfoQueryPayload.IDENTIFIER, MinecraftVersions.V7_2)); + } + + // BungeeGuard forwarding init + if (this.forwarding().enabled() && this.forwarding().mode() == Mode.BUNGEEGUARD) { + logger.debug("Forwarding mode set to BungeeGuard, applying pre-login handler"); + HANDLERS.addFirst(BungeeGuard::validateToken); + } + Constraint.Evaluator.DEBUG = debug; } @@ -225,6 +233,30 @@ public Forwarding forwarding() { @ApiStatus.Internal public void setForwarding(final @NonNull Forwarding forwarding) { + final String modeProperty = System.getProperty("pcf.forwarding.mode"); + if (modeProperty != null) { + PCF.logger.debug("System property forwarding mode: " + modeProperty); + } + final String modeEnv = System.getenv("PCF_FORWARDING_MODE"); + if (modeEnv != null) { + PCF.logger.debug("Environment variable forwarding mode: " + modeEnv); + } + if (modeProperty != null || modeEnv != null) { + final String modeStr = modeEnv != null ? modeEnv : modeProperty; + try { + this.forwarding = + new Forwarding( + forwarding.enabled(), + Mode.valueOf(modeStr.toUpperCase()), + forwarding.secret(), + forwarding.approvedProxyHosts()); + return; + } catch (final IllegalArgumentException e) { + logger.warn( + "Invalid forwarding mode in environment variable, using config value", e); + } + } + this.forwarding = forwarding; } diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/ConnectionBridge.java b/common/src/main/java/org/adde0109/pcf/forwarding/ConnectionBridge.java new file mode 100644 index 00000000..a1b5db2d --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/ConnectionBridge.java @@ -0,0 +1,64 @@ +package org.adde0109.pcf.forwarding; + +import dev.neuralnexus.taterapi.network.Protocol; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.concurrent.Future; + +import org.adde0109.pcf.PCF; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.net.InetSocketAddress; + +public interface ConnectionBridge { + String HANDLER_PACKET = "packet_handler"; + String HANDLER_SPLITTER = "splitter"; + String HANDLER_PREPENDER = "prepender"; + + @NonNull InetSocketAddress bridge$address(); + + void bridge$address(final @NonNull InetSocketAddress address); + + @NonNull Channel bridge$channel(); + + @Nullable Object bridge$getPacketListener(); + + @Nullable Protocol bridge$protocol(); + + default void bridge$send(final @NonNull Object packet) { + this.bridge$channel().writeAndFlush(packet).addListener(ConnectionBridge::errorListener); + } + + /** + * Injects the packet encoder and decoder into the pipeline to handle login query packets + * + * @param ctx the channel handler context + */ + static void injectIntoPipeline(final @NonNull ChannelHandlerContext ctx) { + if (ctx.pipeline().get(PacketDecoder.NAME) != null + || ctx.pipeline().get(PacketEncoder.NAME) != null) { + return; + } + if (PCF.instance().debug().enabled()) { + PCF.logger.debug( + "Injecting packet handlers into pipeline of " + ctx.channel().remoteAddress()); + } + ctx.channel() + .pipeline() + .addAfter(HANDLER_SPLITTER, PacketDecoder.NAME, new PacketDecoder()) + .addAfter(HANDLER_PREPENDER, PacketEncoder.NAME, new PacketEncoder()); + } + + /** + * Listener for logging errors during packet handling + * + * @param future the future to check for success or failure + */ + static void errorListener(Future future) { + if (!future.isSuccess()) { + PCF.logger.error("An error occurred during packet handling", future.cause()); + } + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/Forwarding.java b/common/src/main/java/org/adde0109/pcf/forwarding/Forwarding.java new file mode 100644 index 00000000..d5ce12bf --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/Forwarding.java @@ -0,0 +1,188 @@ +package org.adde0109.pcf.forwarding; + +import static dev.neuralnexus.taterapi.network.chat.Component.literal; +import static dev.neuralnexus.taterapi.network.chat.Component.translatable; + +import static org.adde0109.pcf.forwarding.ReflectionUtils.attributeKeyValueOf; + +import com.mojang.authlib.GameProfile; + +import dev.neuralnexus.taterapi.event.Cancellable; +import dev.neuralnexus.taterapi.mc.server.players.NameAndId; +import dev.neuralnexus.taterapi.network.FriendlyByteBuf; +import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; +import dev.neuralnexus.taterapi.network.protocol.handshake.ClientIntent; +import dev.neuralnexus.taterapi.network.protocol.handshake.ClientIntentionPacket; + +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; + +import org.adde0109.pcf.PCF; +import org.adde0109.pcf.forwarding.legacy.LegacyForwarding; +import org.adde0109.pcf.forwarding.modern.ModernForwarding; +import org.jspecify.annotations.NonNull; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.regex.Pattern; + +public final class Forwarding { + public static final AttributeKey DEFERRED_DISCONNECT = + attributeKeyValueOf("pcf-deferred-disconnect"); + + public static final Object PLAYER_INFO_ERR = literal("Unable to verify player details."); + + private static final Object FAILED_TO_VERIFY = + translatable("multiplayer.disconnect.unverified_username"); + private static final Object REJECTED_PROXY_ERR = literal("Unapproved proxy host."); + + public static final Pattern HOST_PATTERN = Pattern.compile("[0-9a-f.:]{0,45}"); + + /** + * Handle the client intention packet and extract player info + * + * @param connection The connection + * @param data The packet buffer + */ + public static void handleClientIntention( + final @NonNull ConnectionBridge connection, final @NonNull FriendlyByteBuf data) { + final Channel channel = connection.bridge$channel(); + try { + switch (PCF.instance().forwarding().mode()) { + case LEGACY, BUNGEEGUARD -> + LegacyForwarding.handleClientIntention(connection, data); + case MODERN -> ModernForwarding.handleClientIntention(connection, data); + } + } catch (final ThrowingComponent e) { + channel.attr(DEFERRED_DISCONNECT).set(e.getComponent()); + } catch (final Exception e) { + e.printStackTrace(); + channel.attr(DEFERRED_DISCONNECT).set(PLAYER_INFO_ERR); + } + } + + /** + * Abstract implementation of the hello packet handler + * + * @param slpl The ServerLoginPacketListenerImpl + * @param ci The callback info + */ + public static void handleHello( + final @NonNull ServerLoginPacketListenerBridge slpl, final @NonNull CallbackInfo ci) { + try { + final ConnectionBridge connection = slpl.bridge$connection(); + final Channel channel = connection.bridge$channel(); + + // Handle any deferred disconnects from the handshake phase + final Object deferredDisconnect = channel.attr(DEFERRED_DISCONNECT).getAndSet(null); + if (deferredDisconnect != null) { + throw new ThrowingComponent(deferredDisconnect); + } + + switch (PCF.instance().forwarding().mode()) { + case LEGACY, BUNGEEGUARD -> LegacyForwarding.handleHello(slpl, ci); + case MODERN -> ModernForwarding.handleHello(slpl, ci); + } + } catch (final ThrowingComponent e) { + slpl.bridge$disconnect(e.getComponent()); + } catch (final Exception e) { + e.printStackTrace(); + slpl.bridge$disconnect(FAILED_TO_VERIFY); + } finally { + ci.cancel(); + } + } + + /** + * Rewrite ClientIntention packet so the player can enter the login phase. + * + * @param channel The connection's Netty channel + * @param protocolVersion The protocol version from the original packet + * @param hostName The hostname from the original packet + * @param hostPort The port from the original packet + * @param intention The client intention from the original packet + * @param data The packet buffer to write the new packet into + */ + public static void rewriteClientIntention( + final @NonNull Channel channel, + final int protocolVersion, + final String hostName, + final int hostPort, + final ClientIntent intention, + final @NonNull FriendlyByteBuf data) { + final ClientIntentionPacket newPacket = + new ClientIntentionPacket(protocolVersion, hostName, hostPort, intention); + data.clear(); + data.writeVarInt(0x00); + ClientIntentionPacket.STREAM_CODEC.encode(data, newPacket); + PCF.logger.debug("Rewrote ClientIntentionPacket for " + channel.remoteAddress()); + } + + /** + * Checks if the connection is coming from an approved proxy host + * + * @param connection The connection + */ + public static void checkProxy(final @NonNull ConnectionBridge connection) { + final Collection approved = PCF.instance().forwarding().approvedProxyHosts(); + if (!approved.isEmpty()) { + final InetSocketAddress address = connection.bridge$address(); + final String host = address.getHostString(); + final String ip = address.getAddress().getHostAddress(); + if (!approved.contains(host) && !approved.contains(ip)) { + PCF.logger.warn( + "Rejected connection from unapproved proxy host: " + + host + + " (IP: " + + ip + + ")"); + throw new ThrowingComponent(REJECTED_PROXY_ERR); + } + } + } + + /** + * Set the connection's address to the forwarded address from the proxy. + * + * @param connection The connection + * @param forwardedAddress The forwarded address + */ + public static void ipForwarding( + final @NonNull ConnectionBridge connection, + final @NonNull InetAddress forwardedAddress) { + final int port = connection.bridge$address().getPort(); + final InetSocketAddress address = new InetSocketAddress(forwardedAddress, port); + connection.bridge$address(address); + } + + /** + * Pre-login handler that invokes registered {@link PreLoginHandler}s + * + * @param slpl The ServerLoginPacketListenerImpl + * @param profile The player's GameProfile + */ + public static void preLogin( + final @NonNull ServerLoginPacketListenerBridge slpl, + final @NonNull GameProfile profile) { + final Cancellable c = Cancellable.simple(); + try { + for (final PreLoginHandler processor : PreLoginHandler.HANDLERS) { + processor.process(slpl, profile, c); + if (c.cancelled()) { + break; + } + } + } catch (final ThrowingComponent e) { + throw e; + } catch (final Exception e) { + final NameAndId nameAndId = new NameAndId(profile); + PCF.logger.warn("Exception while forwarding user " + nameAndId.name()); + e.printStackTrace(); + throw new ThrowingComponent(FAILED_TO_VERIFY, e); + } finally { + c.cancel(); + } + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/Mode.java b/common/src/main/java/org/adde0109/pcf/forwarding/Mode.java index be35157f..7f2c7478 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/Mode.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/Mode.java @@ -2,5 +2,15 @@ /** Forwarding types supported by PCF */ public enum Mode { - MODERN + LEGACY, + BUNGEEGUARD, + MODERN; + + public boolean isLegacy() { + return this == LEGACY || this == BUNGEEGUARD; + } + + public boolean isModern() { + return this == MODERN; + } } diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/PacketDecoder.java b/common/src/main/java/org/adde0109/pcf/forwarding/PacketDecoder.java new file mode 100644 index 00000000..828bc605 --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/PacketDecoder.java @@ -0,0 +1,162 @@ +package org.adde0109.pcf.forwarding; + +import static dev.neuralnexus.taterapi.network.protocol.login.ServerboundHelloPacket.MAX_NAME_LENGTH; + +import static org.adde0109.pcf.forwarding.ConnectionBridge.HANDLER_PACKET; +import static org.adde0109.pcf.forwarding.Forwarding.handleClientIntention; +import static org.adde0109.pcf.forwarding.legacy.LegacyForwarding.PLAYER_NAME; +import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleCustomQueryAnswer; + +import dev.neuralnexus.taterapi.meta.Constraint; +import dev.neuralnexus.taterapi.meta.MinecraftVersions; +import dev.neuralnexus.taterapi.network.FriendlyByteBuf; +import dev.neuralnexus.taterapi.network.Protocol; +import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; + +import org.adde0109.pcf.PCF; +import org.jspecify.annotations.NonNull; + +import java.nio.channels.ClosedChannelException; +import java.util.List; + +public final class PacketDecoder extends MessageToMessageDecoder { + public static final String NAME = "pcf-decoder"; + + @SuppressWarnings("RedundantThrows") + @Override + protected void decode( + final @NonNull ChannelHandlerContext ctx, + final @NonNull ByteBuf msg, + final List out) + throws Exception { + if (!msg.isReadable()) { + return; + } + final ConnectionBridge connection = + ((ConnectionBridge) ctx.channel().pipeline().get(HANDLER_PACKET)); + + if (connection.bridge$protocol() != Protocol.HANDSHAKING + && connection.bridge$protocol() != Protocol.LOGIN) { + out.add(msg.retain()); + return; + } + + final int readerIndex = msg.readerIndex(); + final FriendlyByteBuf data = new FriendlyByteBuf(msg); + final int id = data.readVarInt(); + final StringBuilder debugInfo = + new StringBuilder("Received ") + .append(connection.bridge$protocol()) + .append(" packet with ID 0x") + .append(Integer.toHexString(id)) + .append(" from ") + .append(ctx.channel().remoteAddress()); + + switch (connection.bridge$protocol()) { + case HANDSHAKING -> { + //noinspection SwitchStatementWithTooFewBranches + switch (id) { + case 0x00 -> { + debugInfo.append(", Handling ClientIntentionPacket"); + + // Rewrite the packet + handleClientIntention(connection, data); + msg.readerIndex(readerIndex); + } + default -> msg.readerIndex(readerIndex); + } + } + case LOGIN -> { + if (!(connection.bridge$getPacketListener() + instanceof ServerLoginPacketListenerBridge slpl)) { + msg.readerIndex(readerIndex); + break; + } + + switch (id) { + case 0x00 -> { + if (!PCF.instance().forwarding().mode().isLegacy()) { + msg.readerIndex(readerIndex); + break; + } + + // TODO: Resolve out of band PLAY accept teleportation packet + // Effects: 1.20.2 - 1.20.4 + if (data.readableBytes() == 1 + && Constraint.range( + MinecraftVersions.V20_2, MinecraftVersions.V20_4) + .result()) { + msg.readerIndex(readerIndex); + debugInfo + .append( + ", Deferring out-of-band PLAY accept teleportation packet:") + .append("\n - Packet Length: ") + .append(data.readableBytes()) + .append("\n - Packet data: 0x") + .append(ByteBufUtil.prettyHexDump(data)); + break; + } + debugInfo.append(", Handling ServerBoundHelloPacket"); + + // Save player name + final String name = data.readUtf(MAX_NAME_LENGTH); + ctx.channel().attr(PLAYER_NAME).set(name); + msg.readerIndex(readerIndex); + } + case 0x02 -> { + if (!PCF.instance().forwarding().mode().isModern()) { + msg.readerIndex(readerIndex); + break; + } + debugInfo.append(", Handling ServerboundCustomQueryAnswerPacket"); + + boolean handled = false; + try { + handled = handleCustomQueryAnswer(slpl, data); + } catch (final ThrowingComponent e) { + handled = true; + slpl.bridge$disconnect(e.getComponent()); + } finally { + if (handled) { + msg.clear(); + } else { + msg.readerIndex(readerIndex); + } + } + } + default -> msg.readerIndex(readerIndex); + } + } + case null, default -> msg.readerIndex(readerIndex); + } + if (PCF.instance().debug().enabled()) { + PCF.logger.debug(debugInfo.toString()); + } + + if (msg.isReadable()) { + out.add(msg.retain()); + } + } + + @Override + public void exceptionCaught( + final @NonNull ChannelHandlerContext ctx, final @NonNull Throwable cause) + throws Exception { + if (cause instanceof ClosedChannelException) { + super.exceptionCaught(ctx, cause); + return; + } + PCF.logger.error( + "Exception in PacketDecoder for " + + ctx.channel().remoteAddress() + + ": " + + cause.getMessage(), + cause); + super.exceptionCaught(ctx, cause); + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketEncoder.java b/common/src/main/java/org/adde0109/pcf/forwarding/PacketEncoder.java similarity index 96% rename from common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketEncoder.java rename to common/src/main/java/org/adde0109/pcf/forwarding/PacketEncoder.java index 1de935fe..0097013b 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketEncoder.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/PacketEncoder.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.forwarding.modern; +package org.adde0109.pcf.forwarding; import dev.neuralnexus.taterapi.network.FriendlyByteBuf; import dev.neuralnexus.taterapi.network.protocol.login.ClientboundCustomQueryPacket; diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/PreLoginHandler.java b/common/src/main/java/org/adde0109/pcf/forwarding/PreLoginHandler.java new file mode 100644 index 00000000..0baf0075 --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/PreLoginHandler.java @@ -0,0 +1,39 @@ +package org.adde0109.pcf.forwarding; + +import com.mojang.authlib.GameProfile; + +import dev.neuralnexus.taterapi.event.Cancellable; +import dev.neuralnexus.taterapi.mc.server.players.NameAndId; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +@ApiStatus.Internal +@FunctionalInterface +public interface PreLoginHandler { + PreLoginHandler DEFAULT_HANDLER = + (slpl, profile, _) -> { + final NameAndId nameAndId = new NameAndId(profile); + slpl.bridge$logger_info( + "UUID of player {} is {}", nameAndId.name(), nameAndId.id()); + slpl.bridge$startClientVerification(profile); + }; + @ApiStatus.Internal List HANDLERS = new ArrayList<>(); + + /** + * Process the forwarded profile + * + * @param slpl the ServerLoginPacketListener + * @param profile the forwarded GameProfile + * @param c the cancellable wrapper + * @throws Exception if an error occurs + */ + void process( + final @NonNull ServerLoginPacketListenerBridge slpl, + final @NonNull GameProfile profile, + final @NonNull Cancellable c) + throws Exception; +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/ReflectionUtils.java b/common/src/main/java/org/adde0109/pcf/forwarding/ReflectionUtils.java new file mode 100644 index 00000000..359f4881 --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/ReflectionUtils.java @@ -0,0 +1,188 @@ +package org.adde0109.pcf.forwarding; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import dev.neuralnexus.taterapi.meta.Constraint; +import dev.neuralnexus.taterapi.meta.MetaAPI; +import dev.neuralnexus.taterapi.meta.MinecraftVersions; +import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; + +import io.netty.util.AttributeKey; + +import net.minecraft.server.MinecraftServer; + +import org.adde0109.pcf.PCF; +import org.jspecify.annotations.NonNull; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public final class ReflectionUtils { + private ReflectionUtils() {} + + private static final MethodHandle profilePropertiesHandle; + private static final MethodHandle profilePropertyNameHandle; + private static final MethodHandle profilePropertyValueHandle; + + static { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + // com.mojang:authlib:5.0.0 or newer + final String profilePropertyNameString; + final String profilePropertyValueString; + if (Constraint.noLessThan(MinecraftVersions.V20_2).result()) { + profilePropertyNameString = "name"; + profilePropertyValueString = "value"; + } else { + profilePropertyNameString = "getName"; + profilePropertyValueString = "getValue"; + } + try { + profilePropertyNameHandle = + lookup.findVirtual( + Property.class, + profilePropertyNameString, + MethodType.methodType(String.class)); + profilePropertyValueHandle = + lookup.findVirtual( + Property.class, + profilePropertyValueString, + MethodType.methodType(String.class)); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException("Failed to initialize GameProfile method handles", e); + } + + // com.mojang:authlib:7.0.0 or newer + if (Constraint.noLessThan(MinecraftVersions.V21_9).result()) { + profilePropertiesHandle = null; + } else { + try { + //noinspection JavaLangInvokeHandleSignature + profilePropertiesHandle = + lookup.findVirtual( + GameProfile.class, + "getProperties", + MethodType.methodType(PropertyMap.class)); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException( + "Failed to initialize GameProfile method handles", e); + } + } + } + + /** + * Gets the name of a Property + * + * @param property the property + * @return the name + */ + public static @NonNull String getName(final @NonNull Property property) { + try { + return (String) profilePropertyNameHandle.invokeExact(property); + } catch (final Throwable e) { + throw new IllegalStateException("Failed to get name from Property", e); + } + } + + /** + * Gets the value of a Property + * + * @param property the property + * @return the value + */ + public static @NonNull String getValue(final @NonNull Property property) { + try { + return (String) profilePropertyValueHandle.invokeExact(property); + } catch (final Throwable e) { + throw new IllegalStateException("Failed to get value from Property", e); + } + } + + /** + * Gets the properties from the given GameProfile - 1.21.8 and older + * + * @param profile the profile + * @return the properties + */ + public static @NonNull PropertyMap getProperties(final @NonNull GameProfile profile) { + try { + return (PropertyMap) profilePropertiesHandle.invokeExact(profile); + } catch (final Throwable e) { + throw new IllegalStateException("Failed to get properties from GameProfile", e); + } + } + + private static final MethodHandle ENFORCE_SECURE_PROFILE; + + static { + MethodHandle enforceSecureProfileHandle = null; + if (Constraint.range(MinecraftVersions.V19, MinecraftVersions.V19_2).result()) { + try { + Class minecraftServerClass = MinecraftServer.class; + //noinspection JavaLangInvokeHandleSignature + enforceSecureProfileHandle = + MethodHandles.lookup() + .findVirtual( + minecraftServerClass, + "m_214005_", // enforceSecureProfile + MethodType.methodType(boolean.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + PCF.logger.error( + "Failed to get MethodHandle for MinecraftServer.enforceSecureProfile", e); + } + } + ENFORCE_SECURE_PROFILE = enforceSecureProfileHandle; + } + + public static boolean enforceSecureProfile() { + if (ENFORCE_SECURE_PROFILE == null) { + return false; + } + try { + return (boolean) + ENFORCE_SECURE_PROFILE.invokeExact( + (MinecraftServer) MetaAPI.instance().server()); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private static final MethodHandle ATTRIBUTE_KEY_VALUE_OF; + + static { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle attributeKeyValueOfHandle; + try { + if (Constraint.noLessThan(MinecraftVersion.V8).result()) { + final Class attributeKeyClass = Class.forName("io.netty.util.AttributeKey"); + attributeKeyValueOfHandle = + lookup.findStatic( + attributeKeyClass, + "valueOf", + MethodType.methodType(AttributeKey.class, String.class)); + } else { // Get old AttributeKey constructor: AttributeKey(String name) + final Class attributeKeyClass = Class.forName("io.netty.util.AttributeKey"); + //noinspection JavaLangInvokeHandleSignature + attributeKeyValueOfHandle = + lookup.findConstructor( + attributeKeyClass, MethodType.methodType(void.class, String.class)); + } + } catch (final Throwable e) { + throw new IllegalStateException( + "Failed to initialize AttributeKey.valueOf MethodHandle", e); + } + ATTRIBUTE_KEY_VALUE_OF = attributeKeyValueOfHandle; + } + + @SuppressWarnings("unchecked") + public static AttributeKey attributeKeyValueOf(final @NonNull String name) { + try { + return (AttributeKey) ATTRIBUTE_KEY_VALUE_OF.invokeExact(name); + } catch (final Throwable e) { + throw new IllegalStateException("Failed to create AttributeKey with name: " + name, e); + } + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ServerLoginPacketListenerBridge.java b/common/src/main/java/org/adde0109/pcf/forwarding/ServerLoginPacketListenerBridge.java similarity index 89% rename from common/src/main/java/org/adde0109/pcf/forwarding/modern/ServerLoginPacketListenerBridge.java rename to common/src/main/java/org/adde0109/pcf/forwarding/ServerLoginPacketListenerBridge.java index 0de20f66..6eb1677c 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ServerLoginPacketListenerBridge.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/ServerLoginPacketListenerBridge.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.forwarding.modern; +package org.adde0109.pcf.forwarding; import com.mojang.authlib.GameProfile; @@ -10,10 +10,6 @@ import java.util.UUID; public interface ServerLoginPacketListenerBridge { - int bridge$velocityLoginMessageId(); - - void bridge$setVelocityLoginMessageId(final int id); - @NonNull ConnectionBridge bridge$connection(); void bridge$disconnect(final @NonNull Object reason); diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/bungeeguard/BungeeGuard.java b/common/src/main/java/org/adde0109/pcf/forwarding/bungeeguard/BungeeGuard.java new file mode 100644 index 00000000..77ca116d --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/bungeeguard/BungeeGuard.java @@ -0,0 +1,66 @@ +package org.adde0109.pcf.forwarding.bungeeguard; + +import static dev.neuralnexus.taterapi.network.chat.Component.literal; + +import static org.adde0109.pcf.forwarding.ReflectionUtils.attributeKeyValueOf; + +import com.mojang.authlib.GameProfile; + +import dev.neuralnexus.taterapi.event.Cancellable; +import dev.neuralnexus.taterapi.mc.server.players.NameAndId; +import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; + +import io.netty.util.AttributeKey; + +import org.adde0109.pcf.PCF; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; +import org.jspecify.annotations.NonNull; + +import java.util.Collection; + +/** Adapted from BungeeGuard */ +public final class BungeeGuard { + public static final String BUNGEE_GUARD_TOKEN_PROPERTY_NAME = "bungeeguard-token"; + public static final AttributeKey> BUNGEE_GUARD_TOKEN = + attributeKeyValueOf("pcf-bungeeguard-token"); + + private static final Object NO_DATA = + literal("&cUnable to authenticate - no data was forwarded by the proxy."); + private static final Object INVALID_TOKEN = literal("&cUnable to authenticate."); + + /** + * Validates the BungeeGuard token for the given connection + * + * @param slpl The ServerLoginPacketListenerImpl + * @param profile The player's GameProfile + * @param c Cancellable + */ + public static void validateToken( + final @NonNull ServerLoginPacketListenerBridge slpl, + final @NonNull GameProfile profile, + final @NonNull Cancellable c) { + final NameAndId nameAndId = new NameAndId(profile); + final String connectionDescription = + nameAndId.id() + " @ " + slpl.bridge$connection().bridge$address().getHostString(); + final Collection bungeeGuardTokens = + slpl.bridge$connection().bridge$channel().attr(BUNGEE_GUARD_TOKEN).getAndSet(null); + if (bungeeGuardTokens.size() > 1) { + PCF.logger.warn( + "Denying connection from " + connectionDescription + " - more than one token"); + c.cancel(); + throw new ThrowingComponent(INVALID_TOKEN); + } + final String bungeeGuardToken = bungeeGuardTokens.stream().findFirst().orElse(null); + + // TODO: Consider supporting multiple in PCF's config + if (!PCF.instance().forwarding().secret().equals(bungeeGuardToken)) { + final String reason = bungeeGuardToken == null ? "No Token" : "Invalid token"; + PCF.logger.warn( + "Denying connection from " + connectionDescription + " - reason: " + reason); + c.cancel(); + throw new ThrowingComponent(bungeeGuardToken == null ? NO_DATA : INVALID_TOKEN); + } + + PCF.logger.debug("Successfully validated BungeeGuard token for " + connectionDescription); + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/ArclightPreLogin.java b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/ArclightPreLogin.java index f2d8c4c8..97522e27 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/ArclightPreLogin.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/ArclightPreLogin.java @@ -2,7 +2,7 @@ import com.mojang.authlib.GameProfile; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import java.lang.invoke.MethodHandle; diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/MohistPreLogin.java b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/MohistPreLogin.java index 230a7eaa..f225417a 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/MohistPreLogin.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/MohistPreLogin.java @@ -2,7 +2,7 @@ import com.mojang.authlib.GameProfile; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import java.lang.invoke.MethodHandle; diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpigotPreLogin.java b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpigotPreLogin.java index 789b374c..dd427260 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpigotPreLogin.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpigotPreLogin.java @@ -2,7 +2,7 @@ import com.mojang.authlib.GameProfile; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import java.lang.invoke.MethodHandle; diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpongePreLogin.java b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpongePreLogin.java index 6f2bec3a..f16ac0c7 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpongePreLogin.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/compatibility/prelogin/SpongePreLogin.java @@ -1,6 +1,6 @@ package org.adde0109.pcf.forwarding.compatibility.prelogin; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jetbrains.annotations.NotNull; import java.lang.invoke.MethodHandle; diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/legacy/LegacyForwarding.java b/common/src/main/java/org/adde0109/pcf/forwarding/legacy/LegacyForwarding.java new file mode 100644 index 00000000..8d0d2111 --- /dev/null +++ b/common/src/main/java/org/adde0109/pcf/forwarding/legacy/LegacyForwarding.java @@ -0,0 +1,286 @@ +package org.adde0109.pcf.forwarding.legacy; + +import static dev.neuralnexus.taterapi.network.chat.Component.literal; + +import static org.adde0109.pcf.forwarding.Forwarding.HOST_PATTERN; +import static org.adde0109.pcf.forwarding.Forwarding.PLAYER_INFO_ERR; +import static org.adde0109.pcf.forwarding.Forwarding.rewriteClientIntention; +import static org.adde0109.pcf.forwarding.ReflectionUtils.attributeKeyValueOf; +import static org.adde0109.pcf.forwarding.ReflectionUtils.getName; +import static org.adde0109.pcf.forwarding.ReflectionUtils.getProperties; +import static org.adde0109.pcf.forwarding.ReflectionUtils.getValue; +import static org.adde0109.pcf.forwarding.bungeeguard.BungeeGuard.BUNGEE_GUARD_TOKEN; +import static org.adde0109.pcf.forwarding.bungeeguard.BungeeGuard.BUNGEE_GUARD_TOKEN_PROPERTY_NAME; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.net.InetAddresses; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import dev.neuralnexus.taterapi.meta.Constraint; +import dev.neuralnexus.taterapi.meta.MinecraftVersions; +import dev.neuralnexus.taterapi.network.FriendlyByteBuf; +import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; +import dev.neuralnexus.taterapi.network.protocol.handshake.ClientIntent; + +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; + +import org.adde0109.pcf.PCF; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.Forwarding; +import org.adde0109.pcf.forwarding.Mode; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.lang.reflect.Type; +import java.net.InetAddress; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * Adapted from Spigot + * and BungeeForge. + * Additional information sourced from Velocity + * and Waterfall + */ +public final class LegacyForwarding { + public static final AttributeKey FORWARDED_ADDRESS = + attributeKeyValueOf("pcf-forwarded-address"); + public static final AttributeKey PLAYER_NAME = attributeKeyValueOf("pcf-player-name"); + public static final AttributeKey SPOOFED_UUID = attributeKeyValueOf("pcf-spoofed-uuid"); + public static final AttributeKey> SPOOFED_PROFILE = + attributeKeyValueOf("pcf-spoofed-profile"); + + private static final Object LEGACY_DIRECT_CONNECT_ERR = + literal("This server requires you to connect with Velocity or BungeeCord."); + private static final Object BG_CONFIG_ERR = + literal("This server requires the proxy to be configured for BungeeGuard forwarding."); + + private static final Gson GSON = new GsonBuilder().create(); + // Use Type b/c GSON shipped with MC 1.19.2 doesn't have Gson#fromJson(String, TypeToken) + private static final Type profileTypeToken = new TypeToken>() {}.getType(); + + private static final Pattern PROP_PATTERN = Pattern.compile("\\w{0,16}"); + + private static final String LEGACY_FORGE_MARKER = "\0FML\0"; + private static final String EXTRA_DATA_PROPERTY = "extraData"; + private static final String LEGACY_FORGE_CLIENT_PROPERTY = "forgeClient"; + private static final String MODERN_FORGE_CLIENT_PROPERTY = "modernForgeClient"; + private static final String FORGE_CLIENT_TRUE = "true"; + + /** + * Handle the client intention packet and extract player info + * + * @param connection The connection + * @param data The packet buffer + */ + public static void handleClientIntention( + final @NonNull ConnectionBridge connection, final @NonNull FriendlyByteBuf data) { + // Read the original packet + final int protocolVersion = data.readVarInt(); + final String hostName = data.readUtf(Short.MAX_VALUE); + final int hostPort = data.readUnsignedShort(); + final ClientIntent intention = ClientIntent.byId(data.readVarInt()); + if (intention != ClientIntent.LOGIN) { + return; + } + final Channel channel = connection.bridge$channel(); + + // Parse the host name for forwarded data + final String[] split = hostName.split("\0"); + if (split.length < 3 || !(HOST_PATTERN.matcher(split[1]).matches())) { + throw new ThrowingComponent(LEGACY_DIRECT_CONNECT_ERR); + } + if (PCF.instance().forwarding().mode() == Mode.BUNGEEGUARD + && (split.length < 4 || !split[3].contains(BUNGEE_GUARD_TOKEN_PROPERTY_NAME))) { + // Rewrite the packet before throwing + rewriteClientIntention(channel, protocolVersion, split[0], hostPort, intention, data); + throw new ThrowingComponent(BG_CONFIG_ERR); + } + + final String originalHost = split[0]; + final String forwardedAddress = split[1]; + final UUID uuid = fromStringLenient(split[2]); + + // Save forwarded data + channel.attr(FORWARDED_ADDRESS).set(InetAddresses.forString(forwardedAddress)); + channel.attr(SPOOFED_UUID).set(uuid); + + // spotless:off + final boolean legacyForgeClient; + final boolean modernForgeClient; + final Optional extraData; + if (split.length >= 4) { + final String profileJSON = split[3]; + final List properties = GSON.fromJson(profileJSON, profileTypeToken); + + // Pop out the Forge properties + legacyForgeClient = properties.stream().anyMatch(p -> + getName(p).equals(LEGACY_FORGE_CLIENT_PROPERTY) && getValue(p).equals(FORGE_CLIENT_TRUE)); + modernForgeClient = properties.stream().anyMatch(p -> + getName(p).equals(MODERN_FORGE_CLIENT_PROPERTY) && getValue(p).equals(FORGE_CLIENT_TRUE)); + extraData = properties.stream() + .filter(p -> getName(p).equals(EXTRA_DATA_PROPERTY)).findFirst(); + properties.removeIf(p -> getName(p).equals(LEGACY_FORGE_CLIENT_PROPERTY) + || getName(p).equals(MODERN_FORGE_CLIENT_PROPERTY) + || getName(p).equals(EXTRA_DATA_PROPERTY)); + channel.attr(SPOOFED_PROFILE).set(properties); + } else { + legacyForgeClient = false; + modernForgeClient = false; + extraData = Optional.empty(); + } + + final String host; + if (extraData.isPresent()) { + final String value = getValue(extraData.get()); + if (PCF.instance().debug().enabled()) { + if (legacyForgeClient) { + PCF.logger.debug("Received extraData with forgeClient=true from " + + channel.remoteAddress() + " - value: " + value); + } else if (modernForgeClient) { + PCF.logger.debug("Received extraData with modernForgeClient=true from " + + channel.remoteAddress() + " - value: " + value); + } else { // Some implementations do this + PCF.logger.debug("Received extraData without (modernF|f)orgeClient=true from " + + channel.remoteAddress() + " - value: " + value); + } + } + if (value.startsWith("\1")) { // Restore extra hostname data + host = originalHost + value.replace("\1", "\0"); + } else { // Avoid propagating bad data + PCF.logger.warn("Received misformatted extraData from " + + channel.remoteAddress() + " - value: " + value); + host = originalHost; + } + } else if (legacyForgeClient) { // Assume Forge 1.8 - 1.12.2 + if (PCF.instance().debug().enabled()) { + PCF.logger.debug("Identified legacy Forge client from " + channel.remoteAddress() + + " - appending legacy Forge marker to hostname"); + } + host = originalHost + LEGACY_FORGE_MARKER; + } else { + host = originalHost; + } + PCF.logger.debug("Parsed forwarded data - Host: " + host + ", UUID: " + uuid); + // spotless:on + + // Write the original address (and Forge marker) back into packet + rewriteClientIntention(channel, protocolVersion, host, hostPort, intention, data); + } + + /** + * Hello packet handler for legacy forwarding + * + * @param slpl The ServerLoginPacketListenerImpl + * @param ci The callback info + */ + public static void handleHello( + final @NonNull ServerLoginPacketListenerBridge slpl, final @NonNull CallbackInfo ci) { + final ConnectionBridge connection = slpl.bridge$connection(); + final Channel channel = connection.bridge$channel(); + + // Check if the connection is from an approved proxy + Forwarding.checkProxy(connection); + + // Apply IP forwarding + final InetAddress address = channel.attr(FORWARDED_ADDRESS).get(); + Forwarding.ipForwarding(connection, address); + + // Query player info from channel + final String name = channel.attr(PLAYER_NAME).getAndSet(null); + final UUID uuid = channel.attr(SPOOFED_UUID).getAndSet(null); + if (name == null || uuid == null) { + throw new ThrowingComponent(PLAYER_INFO_ERR); + } + final Collection properties = channel.attr(SPOOFED_PROFILE).getAndSet(null); + + // Check for BungeeGuard tokens + if (PCF.instance().forwarding().mode() == Mode.BUNGEEGUARD && properties != null) { + final Collection bungeeGuardTokens = new HashSet<>(); + for (final Property property : properties) { + if (getName(property).equals(BUNGEE_GUARD_TOKEN_PROPERTY_NAME)) { + bungeeGuardTokens.add(getValue(property)); + } + } + channel.attr(BUNGEE_GUARD_TOKEN).set(bungeeGuardTokens); + + // Remove BungeeGuard token(s) from properties. + // They're filtered out by PROP_PATTERN, but might as well remove them now. + properties.removeIf( + property -> getName(property).equals(BUNGEE_GUARD_TOKEN_PROPERTY_NAME)); + } + + // Create the profile + final GameProfile profile = createProfile(name, uuid, properties); + + // Proceed with login + ci.cancel(); + Forwarding.preLogin(slpl, profile); + } + + /** + * Parse a UUID from a string, leniently accepting both dashed and non-dashed formats + * + * @param string The string to parse + * @return The parsed UUID + */ + private static @NonNull UUID fromStringLenient(final @NonNull String string) { + return UUID.fromString( + string.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + } + + /** + * Creates a new GameProfile + * + * @param name The player's name + * @param uuid The player's UUID + * @param properties The player's properties, if any + * @return The created GameProfile + */ + public static @NonNull GameProfile createProfile( + final @NonNull String name, + final @NonNull UUID uuid, + final @Nullable Collection properties) { + // Exit early if there are no properties + if (properties == null || properties.isEmpty()) { + return new GameProfile(uuid, name); + } + + // Filter out invalid properties + properties.removeIf(property -> !PROP_PATTERN.matcher(getName(property)).matches()); + + // Create the profile + if (Constraint.noLessThan(MinecraftVersions.V21_9) + .result()) { // com.mojang:authlib:7.0.0 or newer + final ImmutableMultimap.Builder propertiesBuilder = + ImmutableMultimap.builder(); + for (final Property property : properties) { + propertiesBuilder.put(property.name(), property); + } + return new GameProfile(uuid, name, new PropertyMap(propertiesBuilder.build())); + } else { + final GameProfile profile = new GameProfile(uuid, name); + final PropertyMap propertiesMap = getProperties(profile); + for (final Property property : properties) { + propertiesMap.put(getName(property), property); + } + return profile; + } + } +} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ConnectionBridge.java b/common/src/main/java/org/adde0109/pcf/forwarding/modern/ConnectionBridge.java deleted file mode 100644 index e99e7007..00000000 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ConnectionBridge.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.adde0109.pcf.forwarding.modern; - -import io.netty.channel.Channel; - -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.net.InetSocketAddress; - -public interface ConnectionBridge { - @NonNull InetSocketAddress bridge$address(); - - void bridge$address(final @NonNull InetSocketAddress address); - - @NonNull Channel bridge$channel(); - - @Nullable Object bridge$getPacketListener(); - - default void bridge$send(final @NonNull Object packet) { - this.bridge$channel().writeAndFlush(packet).addListener(ModernForwarding::errorListener); - } -} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ModernForwarding.java b/common/src/main/java/org/adde0109/pcf/forwarding/modern/ModernForwarding.java index 717c4666..695160e8 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ModernForwarding.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/modern/ModernForwarding.java @@ -3,36 +3,36 @@ import static dev.neuralnexus.taterapi.network.chat.Component.literal; import static dev.neuralnexus.taterapi.network.chat.Component.translatable; -import static org.adde0109.pcf.forwarding.modern.ReflectionUtils.enforceSecureProfile; +import static org.adde0109.pcf.forwarding.Forwarding.HOST_PATTERN; +import static org.adde0109.pcf.forwarding.Forwarding.PLAYER_INFO_ERR; +import static org.adde0109.pcf.forwarding.Forwarding.rewriteClientIntention; +import static org.adde0109.pcf.forwarding.ReflectionUtils.attributeKeyValueOf; +import static org.adde0109.pcf.forwarding.ReflectionUtils.enforceSecureProfile; import static org.adde0109.pcf.forwarding.modern.VelocityProxy.MODERN_MAX_VERSION; import static org.adde0109.pcf.forwarding.modern.VelocityProxy.PLAYER_INFO_PAYLOAD; import static org.adde0109.pcf.forwarding.modern.VelocityProxy.checkIntegrity; -import com.mojang.authlib.GameProfile; - -import dev.neuralnexus.taterapi.event.Cancellable; -import dev.neuralnexus.taterapi.mc.server.players.NameAndId; import dev.neuralnexus.taterapi.meta.Constraint; import dev.neuralnexus.taterapi.meta.MinecraftVersions; +import dev.neuralnexus.taterapi.network.FriendlyByteBuf; import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; +import dev.neuralnexus.taterapi.network.protocol.handshake.ClientIntent; import dev.neuralnexus.taterapi.network.protocol.login.ClientboundCustomQueryPacket; import dev.neuralnexus.taterapi.network.protocol.login.ServerboundCustomQueryAnswerPacket; import dev.neuralnexus.taterapi.network.protocol.login.custom.CustomQueryAnswerPayload; -import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.Channel; import io.netty.handler.codec.DecoderException; -import io.netty.util.concurrent.Future; +import io.netty.util.AttributeKey; import org.adde0109.pcf.PCF; -import org.adde0109.pcf.forwarding.Mode; -import org.jetbrains.annotations.ApiStatus; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.Forwarding; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.net.InetSocketAddress; import java.security.InvalidKeyException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** @@ -52,139 +52,96 @@ * 1.20.x */ public final class ModernForwarding { - private static final Object REJECTED_PROXY_ERR = literal("Unapproved proxy host."); + public static final AttributeKey LOGIN_MESSAGE_ID = + attributeKeyValueOf("pcf-login-message-id"); - private static final String HANDLER_SPLITTER = "splitter"; - private static final String HANDLER_PREPENDER = "prepender"; + private static final Object MODERN_DIRECT_CONNECT_ERR = + literal("This server requires you to connect with Velocity."); + private static final Object EMPTY_PAYLOAD_ERR = + literal("Received empty player info payload from the proxy."); + private static final Object MISSING_PROFILE_PUBLIC_KEY = + translatable("multiplayer.disconnect.missing_public_key"); + private static final Object INVALID_SIGNATURE = + translatable("multiplayer.disconnect.invalid_public_key_signature"); /** - * Injects the packet encoder and decoder into the pipeline to handle login query packets + * Handle the client intention packet and reject the connection if legacy forwarding is + * detected. * - * @param connection the connection - * @param ctx the channel handler context + * @param connection The connection + * @param data The packet buffer */ - public static void injectIntoPipeline( - final @NonNull ConnectionBridge connection, final @NonNull ChannelHandlerContext ctx) { - if (ctx.pipeline().get(PacketDecoder.NAME) != null - || ctx.pipeline().get(PacketEncoder.NAME) != null) { + public static void handleClientIntention( + final @NonNull ConnectionBridge connection, final @NonNull FriendlyByteBuf data) { + // Read the original packet + final int protocolVersion = data.readVarInt(); + final String hostName = data.readUtf(Short.MAX_VALUE); + final int hostPort = data.readUnsignedShort(); + final ClientIntent intention = ClientIntent.byId(data.readVarInt()); + if (intention != ClientIntent.LOGIN) { return; } - PCF.logger.debug( - "Injecting packet handlers into pipeline of " + ctx.channel().remoteAddress()); - ctx.channel() - .pipeline() - .addAfter(HANDLER_SPLITTER, PacketDecoder.NAME, new PacketDecoder(connection)) - .addAfter(HANDLER_PREPENDER, PacketEncoder.NAME, new PacketEncoder()); - } + final Channel channel = connection.bridge$channel(); - /** - * Listener for logging errors during packet handling - * - * @param future the future to check for success or failure - */ - public static void errorListener(Future future) { - if (!future.isSuccess()) { - PCF.logger.error("An error occurred during packet handling", future.cause()); + // Parse the host name for forwarded data + final String[] split = hostName.split("\0"); + if (split.length < 3 || !(HOST_PATTERN.matcher(split[1]).matches())) { + return; // Either vanilla or direct modded connection } + + // Rewrite packet + rewriteClientIntention(channel, protocolVersion, split[0], hostPort, intention, data); + + // Disconnect the user + throw new ThrowingComponent(MODERN_DIRECT_CONNECT_ERR); } /** - * Abstract implementation of the hello packet handler + * Hello packet handler for modern forwarding * * @param slpl The ServerLoginPacketListenerImpl * @param ci The callback info */ public static void handleHello( final @NonNull ServerLoginPacketListenerBridge slpl, final @NonNull CallbackInfo ci) { - if (!PCF.instance().forwarding().enabled() - || !PCF.instance().forwarding().mode().equals(Mode.MODERN)) { - return; - } + final ConnectionBridge connection = slpl.bridge$connection(); - final List approved = PCF.instance().forwarding().approvedProxyHosts(); - if (!approved.isEmpty()) { - final InetSocketAddress address = slpl.bridge$connection().bridge$address(); - final String host = address.getHostString(); - final String ip = address.getAddress().getHostAddress(); - if (!approved.contains(host) && !approved.contains(ip)) { - PCF.logger.warn( - "Rejected connection from unapproved proxy host: " - + host - + " (IP: " - + ip - + ")"); - slpl.bridge$disconnect(REJECTED_PROXY_ERR); - ci.cancel(); - return; - } - } + // Check if the connection is from an approved proxy + Forwarding.checkProxy(connection); - slpl.bridge$setVelocityLoginMessageId(ThreadLocalRandom.current().nextInt()); - slpl.bridge$connection() - .bridge$send( - new ClientboundCustomQueryPacket( - slpl.bridge$velocityLoginMessageId(), PLAYER_INFO_PAYLOAD)); + // Send forwarding request + final int messageId = ThreadLocalRandom.current().nextInt(); + connection.bridge$channel().attr(LOGIN_MESSAGE_ID).set(messageId); + connection.bridge$send(new ClientboundCustomQueryPacket(messageId, PLAYER_INFO_PAYLOAD)); PCF.logger.debug("Sent Forward Request"); ci.cancel(); } - @ApiStatus.Internal - @FunctionalInterface - public interface PostProcessor { - /** - * Process the forwarded profile - * - * @param slpl the ServerLoginPacketListener - * @param profile the forwarded GameProfile - * @param c the cancellable wrapper - * @throws Exception if an error occurs - */ - void process( - final @NonNull ServerLoginPacketListenerBridge slpl, - final @NonNull GameProfile profile, - final @NonNull Cancellable c) - throws Exception; - } - - private static final PostProcessor DEFAULT_POST_PROCESSOR = - (slpl, profile, _) -> { - final NameAndId nameAndId = new NameAndId(profile); - slpl.bridge$logger_info( - "UUID of player {} is {}", nameAndId.name(), nameAndId.id()); - slpl.bridge$startClientVerification(profile); - }; - - @ApiStatus.Internal - public static final List postProcessors = - new ArrayList<>(List.of(DEFAULT_POST_PROCESSOR)); - - private static final Object DIRECT_CONNECT_ERR = - literal("This server requires you to connect with Velocity."); - private static final Object EMPTY_PAYLOAD_ERR = - literal("Received empty player info payload from the proxy."); - private static final Object PLAYER_INFO_ERR = literal("Unable to verify player details."); - private static final Object FAILED_TO_VERIFY = - translatable("multiplayer.disconnect.unverified_username"); - private static final Object MISSING_PROFILE_PUBLIC_KEY = - translatable("multiplayer.disconnect.missing_public_key"); - private static final Object INVALID_SIGNATURE = - translatable("multiplayer.disconnect.invalid_public_key_signature"); - /** - * Abstract implementation of the custom query packet handler + * CustomQueryAnswer packet handler for modern forwarding * * @param slpl The ServerLoginPacketListenerImpl - * @param packet The Minecraft packet + * @param data The packet buffer */ - public static void handleCustomQueryPacket( + public static boolean handleCustomQueryAnswer( final @NonNull ServerLoginPacketListenerBridge slpl, - final @NonNull ServerboundCustomQueryAnswerPacket packet) { + final @NonNull FriendlyByteBuf data) { + final ConnectionBridge connection = slpl.bridge$connection(); + final ServerboundCustomQueryAnswerPacket packet = + ServerboundCustomQueryAnswerPacket.STREAM_CODEC.decode(data); + + // Check if the packet should be handled + if (packet.transactionId() != connection.bridge$channel().attr(LOGIN_MESSAGE_ID).get()) { + return false; + } + + // Decode raw buffer final CustomQueryAnswerPayload.Raw rawPayload = packet.payload() instanceof CustomQueryAnswerPayload.Raw raw ? raw : null; // Validate payload presence if (rawPayload == null) { - throw new ThrowingComponent(DIRECT_CONNECT_ERR); + throw new ThrowingComponent(MODERN_DIRECT_CONNECT_ERR); } else if (rawPayload.data().readableBytes() == 0) { PCF.logger.error( "Received empty forwarding payload. Has Velocity been configured to use modern forwarding?"); @@ -231,9 +188,7 @@ public static void handleCustomQueryPacket( PCF.logger.debug("Using modern forwarding version: " + version); // Apply IP forwarding - final int port = slpl.bridge$connection().bridge$address().getPort(); - final InetSocketAddress address = new InetSocketAddress(payload.address(), port); - slpl.bridge$connection().bridge$address(address); + Forwarding.ipForwarding(connection, payload.address()); // Handle profile key switch (version) { @@ -275,19 +230,7 @@ public static void handleCustomQueryPacket( } // Proceed with login - try { - final Cancellable cancellable = Cancellable.simple(); - for (final PostProcessor processor : postProcessors) { - processor.process(slpl, payload.profile(), cancellable); - if (cancellable.cancelled()) { - break; - } - } - } catch (final Exception e) { - final NameAndId nameAndId = new NameAndId(payload.profile()); - PCF.logger.warn("Exception while forwarding user " + nameAndId.name()); - e.printStackTrace(); - throw new ThrowingComponent(FAILED_TO_VERIFY, e); - } + Forwarding.preLogin(slpl, payload.profile()); + return true; } } diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketDecoder.java b/common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketDecoder.java deleted file mode 100644 index d569e47c..00000000 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/PacketDecoder.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.adde0109.pcf.forwarding.modern; - -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleCustomQueryPacket; - -import dev.neuralnexus.taterapi.network.FriendlyByteBuf; -import dev.neuralnexus.taterapi.network.chat.ThrowingComponent; -import dev.neuralnexus.taterapi.network.protocol.login.ServerboundCustomQueryAnswerPacket; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; - -import org.adde0109.pcf.PCF; -import org.jspecify.annotations.NonNull; - -import java.util.List; - -public final class PacketDecoder extends MessageToMessageDecoder { - public static final String NAME = "pcf-decoder"; - - private final ConnectionBridge connection; - - public PacketDecoder(final @NonNull ConnectionBridge connection) { - this.connection = connection; - } - - @SuppressWarnings("RedundantThrows") - @Override - protected void decode( - final @NonNull ChannelHandlerContext ctx, - final @NonNull ByteBuf msg, - final List out) - throws Exception { - if (!msg.isReadable()) { - return; - } - if (!(this.connection.bridge$getPacketListener() - instanceof ServerLoginPacketListenerBridge slpl)) { - out.add(msg.retain()); - return; - } - - final int readerIndex = msg.readerIndex(); - final FriendlyByteBuf data = new FriendlyByteBuf(msg); - final int id = data.readVarInt(); - PCF.logger.debug( - "Received packet with ID 0x" - + Integer.toHexString(id) - + " from " - + ctx.channel().remoteAddress()); - - //noinspection SwitchStatementWithTooFewBranches - switch (id) { - case 0x02 -> { - final ServerboundCustomQueryAnswerPacket packet = - ServerboundCustomQueryAnswerPacket.STREAM_CODEC.decode(data); - - // Check if the packet should be handled - if (packet.transactionId() != slpl.bridge$velocityLoginMessageId()) { - msg.readerIndex(readerIndex); - break; - } - PCF.logger.debug( - "Handling ServerboundCustomQueryAnswerPacket from " - + ctx.channel().remoteAddress()); - - try { - handleCustomQueryPacket(slpl, packet); - } catch (final ThrowingComponent e) { - slpl.bridge$disconnect(e.getComponent()); - } finally { - msg.clear(); - } - } - // Reset reader index for unhandled packets - default -> msg.readerIndex(readerIndex); - } - - if (msg.isReadable()) { - out.add(msg.retain()); - } - } -} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ReflectionUtils.java b/common/src/main/java/org/adde0109/pcf/forwarding/modern/ReflectionUtils.java deleted file mode 100644 index 69bf85cc..00000000 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/ReflectionUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.adde0109.pcf.forwarding.modern; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.PropertyMap; - -import dev.neuralnexus.taterapi.meta.Constraint; -import dev.neuralnexus.taterapi.meta.MetaAPI; -import dev.neuralnexus.taterapi.meta.MinecraftVersions; - -import net.minecraft.server.MinecraftServer; - -import org.adde0109.pcf.PCF; -import org.jspecify.annotations.NonNull; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -public final class ReflectionUtils { - private ReflectionUtils() {} - - private static final MethodHandle profilePropertiesHandle; - - static { - // com.mojang:authlib:7.0.0 or newer - if (Constraint.noLessThan(MinecraftVersions.V21_9).result()) { - profilePropertiesHandle = null; - } else { - try { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - //noinspection JavaLangInvokeHandleSignature - profilePropertiesHandle = - lookup.findVirtual( - GameProfile.class, - "getProperties", - MethodType.methodType(PropertyMap.class)); - } catch (final NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException( - "Failed to initialize GameProfile method handles", e); - } - } - } - - /** - * Gets the properties from the given GameProfile - 1.21.8 and older - * - * @param profile the profile - * @return the properties - */ - static @NonNull PropertyMap getProperties(final @NonNull GameProfile profile) { - try { - return (PropertyMap) profilePropertiesHandle.invokeExact(profile); - } catch (final Throwable e) { - throw new IllegalStateException("Failed to get properties from GameProfile", e); - } - } - - private static final MethodHandle ENFORCE_SECURE_PROFILE; - - static { - MethodHandle enforceSecureProfileHandle = null; - if (Constraint.range(MinecraftVersions.V19, MinecraftVersions.V19_2).result()) { - try { - Class minecraftServerClass = MinecraftServer.class; - //noinspection JavaLangInvokeHandleSignature - enforceSecureProfileHandle = - MethodHandles.lookup() - .findVirtual( - minecraftServerClass, - "m_214005_", // enforceSecureProfile - MethodType.methodType(boolean.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - PCF.logger.error( - "Failed to get MethodHandle for MinecraftServer.enforceSecureProfile", e); - } - } - ENFORCE_SECURE_PROFILE = enforceSecureProfileHandle; - } - - static boolean enforceSecureProfile() { - if (ENFORCE_SECURE_PROFILE == null) { - return false; - } - try { - return (boolean) - ENFORCE_SECURE_PROFILE.invokeExact( - (MinecraftServer) MetaAPI.instance().server()); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } -} diff --git a/common/src/main/java/org/adde0109/pcf/forwarding/modern/VelocityProxy.java b/common/src/main/java/org/adde0109/pcf/forwarding/modern/VelocityProxy.java index b5f8be1a..b82ce9d9 100644 --- a/common/src/main/java/org/adde0109/pcf/forwarding/modern/VelocityProxy.java +++ b/common/src/main/java/org/adde0109/pcf/forwarding/modern/VelocityProxy.java @@ -2,7 +2,7 @@ import static dev.neuralnexus.taterapi.resources.Identifier.identifier; -import static org.adde0109.pcf.forwarding.modern.ReflectionUtils.getProperties; +import static org.adde0109.pcf.forwarding.ReflectionUtils.getProperties; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; diff --git a/common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/modern/ConnectionMixin.java b/common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/ConnectionMixin.java similarity index 88% rename from common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/modern/ConnectionMixin.java rename to common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/ConnectionMixin.java index 238fd4da..e0d8fd6b 100644 --- a/common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/modern/ConnectionMixin.java +++ b/common/src/main/java/org/adde0109/pcf/mixin/common/forwarding/ConnectionMixin.java @@ -1,6 +1,4 @@ -package org.adde0109.pcf.mixin.common.forwarding.modern; - -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.injectIntoPipeline; +package org.adde0109.pcf.mixin.common.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -11,7 +9,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -41,7 +39,7 @@ public abstract static class Mojang implements ConnectionBridge { @Inject(method = "channelActive", at = @At("TAIL"), remap = false) private void onChannelActive( final @NonNull ChannelHandlerContext ctx, final @NonNull CallbackInfo ci) { - injectIntoPipeline(this, ctx); + ConnectionBridge.injectIntoPipeline(ctx); } } @@ -61,7 +59,7 @@ public abstract static class Searge implements ConnectionBridge { @Inject(method = "channelActive", at = @At("TAIL"), remap = false) private void onChannelActive( final @NonNull ChannelHandlerContext ctx, final @NonNull CallbackInfo ci) { - injectIntoPipeline(this, ctx); + ConnectionBridge.injectIntoPipeline(ctx); } } } diff --git a/common/src/main/java/org/adde0109/pcf/mixin/plugin/PCFMixinPlugin.java b/common/src/main/java/org/adde0109/pcf/mixin/plugin/PCFMixinPlugin.java index 2c0cb1fd..a1f9d797 100644 --- a/common/src/main/java/org/adde0109/pcf/mixin/plugin/PCFMixinPlugin.java +++ b/common/src/main/java/org/adde0109/pcf/mixin/plugin/PCFMixinPlugin.java @@ -10,7 +10,6 @@ import dev.neuralnexus.taterapi.muxins.Muxins; import org.adde0109.pcf.PCF; -import org.adde0109.pcf.forwarding.Mode; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; @@ -82,10 +81,17 @@ private static boolean shouldApplyMixin( PCF.logger.debug("Skipping mixin " + m + " because forwarding is disabled."); return false; } - if (forwarding.mode() != Mode.MODERN && m.contains(".forwarding.modern.")) { + if (!forwarding.mode().isModern() && m.contains(".forwarding.modern.")) { PCF.logger.debug("Skipping mixin " + m + " because forwarding mode is not MODERN."); return false; } + if (!forwarding.mode().isLegacy() && m.contains(".forwarding.legacy.")) { + PCF.logger.debug( + "Skipping mixin " + + m + + " because forwarding mode is not LEGACY or BUNGEEGUARD."); + return false; + } if (advanced.modernForwardingVersion() != NO_OVERRIDE && Constraint.builder().version(MinecraftVersions.V19).result() && advanced.modernForwardingVersion() != MODERN_FORWARDING_WITH_KEY diff --git a/common/src/main/resources/pcf.mixins.common.json b/common/src/main/resources/pcf.mixins.common.json index 204d63a5..f4833e26 100644 --- a/common/src/main/resources/pcf.mixins.common.json +++ b/common/src/main/resources/pcf.mixins.common.json @@ -8,7 +8,7 @@ "plugin": "org.adde0109.pcf.mixin.plugin.PCFMixinPlugin", "package": "org.adde0109.pcf.mixin", "server": [ - "common.forwarding.modern.ConnectionMixin$Mojang", - "common.forwarding.modern.ConnectionMixin$Searge" + "common.forwarding.ConnectionMixin$Mojang", + "common.forwarding.ConnectionMixin$Searge" ] } diff --git a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ConnectionMixin.java b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ConnectionMixin.java similarity index 72% rename from deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ConnectionMixin.java rename to deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ConnectionMixin.java index 42c8e95c..0a96af26 100644 --- a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ConnectionMixin.java +++ b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ConnectionMixin.java @@ -1,12 +1,13 @@ -package org.adde0109.pcf.mixin.v26_1.forwarding.modern; +package org.adde0109.pcf.mixin.v26_1.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; +import dev.neuralnexus.taterapi.network.Protocol; import net.minecraft.network.Connection; import net.minecraft.network.PacketListener; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -37,4 +38,13 @@ public abstract class ConnectionMixin implements ConnectionBridge { public @Nullable Object bridge$getPacketListener() { return this.shadow$getPacketListener(); } + + @Override + public Protocol bridge$protocol() { + final PacketListener listener = this.shadow$getPacketListener(); + if (listener == null) { + return null; + } + return Protocol.fromId(listener.protocol().id()); + } } diff --git a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloHybridMixin.java b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloHybridMixin.java similarity index 91% rename from deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloHybridMixin.java rename to deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloHybridMixin.java index b2a682bf..859901b4 100644 --- a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloHybridMixin.java +++ b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloHybridMixin.java @@ -1,6 +1,6 @@ -package org.adde0109.pcf.mixin.v26_1.forwarding.modern; +package org.adde0109.pcf.mixin.v26_1.forwarding; -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleHello; +import static org.adde0109.pcf.forwarding.Forwarding.handleHello; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -10,7 +10,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.commons.lang3.Validate; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.Mixin; diff --git a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloMixin.java similarity index 93% rename from deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java rename to deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloMixin.java index 77b9dc98..2d6d44c7 100644 --- a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java +++ b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplHelloMixin.java @@ -1,6 +1,6 @@ -package org.adde0109.pcf.mixin.v26_1.forwarding.modern; +package org.adde0109.pcf.mixin.v26_1.forwarding; -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleHello; +import static org.adde0109.pcf.forwarding.Forwarding.handleHello; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -9,7 +9,7 @@ import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; import dev.neuralnexus.taterapi.meta.enums.Platform; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; diff --git a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 79% rename from deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplMixin.java index f1592689..46338c28 100644 --- a/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/deobsf/v26_1/src/main/java/org/adde0109/pcf/mixin/v26_1/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v26_1.forwarding.modern; +package org.adde0109.pcf.mixin.v26_1.forwarding; import com.mojang.authlib.GameProfile; @@ -11,14 +11,13 @@ import net.minecraft.network.chat.Component; import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; @AConstraint(mappings = Mappings.MOJANG, version = @Versions(min = MinecraftVersion.V20_2)) @Mixin(ServerLoginPacketListenerImpl.class) @@ -30,19 +29,8 @@ public abstract class ServerLoginPacketListenerImplMixin @Shadow public abstract void shadow$disconnect(Component details); @Shadow protected abstract void shadow$startClientVerification(GameProfile profile); @Shadow private GameProfile authenticatedProfile; - @Unique private int pcf$velocityLoginMessageId = -1; // spotless:on - @Override - public int bridge$velocityLoginMessageId() { - return this.pcf$velocityLoginMessageId; - } - - @Override - public void bridge$setVelocityLoginMessageId(final int id) { - this.pcf$velocityLoginMessageId = id; - } - @Override public @NonNull ConnectionBridge bridge$connection() { return (ConnectionBridge) this.connection; diff --git a/deobsf/v26_1/src/main/resources/pcf.mixins.v26_1.json b/deobsf/v26_1/src/main/resources/pcf.mixins.v26_1.json index b59a9619..334c7601 100644 --- a/deobsf/v26_1/src/main/resources/pcf.mixins.v26_1.json +++ b/deobsf/v26_1/src/main/resources/pcf.mixins.v26_1.json @@ -10,9 +10,9 @@ "server": [ "v26_1.crossstich.ArgumentNodeStubMixin", "v26_1.crossstich.ArgumentTypeInfoMixin", - "v26_1.forwarding.modern.ConnectionMixin", - "v26_1.forwarding.modern.ServerLoginPacketListenerImplHelloHybridMixin", - "v26_1.forwarding.modern.ServerLoginPacketListenerImplHelloMixin", - "v26_1.forwarding.modern.ServerLoginPacketListenerImplMixin" + "v26_1.forwarding.ConnectionMixin", + "v26_1.forwarding.ServerLoginPacketListenerImplMixin", + "v26_1.forwarding.ServerLoginPacketListenerImplHelloHybridMixin", + "v26_1.forwarding.ServerLoginPacketListenerImplHelloMixin" ] } diff --git a/gradle.properties b/gradle.properties index 086c9c87..2f6c4f90 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ mod_id=pcf mod_name=ProxyCompatibleForge description=Bring modern forwarding to Neo/Forge servers, requires the Ambassador plugin on Velocity for 1.13-1.20.1, should work standalone on 1.20.2+ group=org.adde0109.pcf -version=1.2.6 +version=1.3.0-SNAPSHOT license=LGPL-2.1 authors=adde0109,JT122406,p0t4t0sandwich java_version=25 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82fc455c..f570d05b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,13 +5,14 @@ netty = "4.1.128.Final" nightconfig = "3.8.4" shadow = "9.4.1" spotless = "8.4.0" -taterlib-lite = "0.3.0" +taterlib-lite = "0.3.1-SNAPSHOT" unimined = "1.3.16-SNAPSHOT" [libraries] annotations = { group = "org.jetbrains", name = "annotations", version = "26.0.2" } asm-tree = { group = "org.ow2.asm", name = "asm-tree", version = "6.2" } entrypoint-spoof = { group = "dev.neuralnexus", name = "entrypoint-spoof", version = "0.1.30" } +gson = { group = "com.google.code.gson", name = "gson", version = "2.13.2" } guava = { group = "com.google.guava", name = "guava", version = "33.3.1-jre" } jspecify = { group = "org.jspecify", name = "jspecify", version = "1.0.0" } mixin = { group = "org.spongepowered", name = "mixin", version = "0.8.5-SNAPSHOT" } diff --git a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java b/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java similarity index 86% rename from legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java rename to legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java index 232dbb0a..c3d50b3f 100644 --- a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplHelloMixin.java +++ b/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java @@ -1,6 +1,6 @@ -package org.adde0109.pcf.mixin.v12_2.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v12_2.forge.forwarding; -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleHello; +import static org.adde0109.pcf.forwarding.Forwarding.handleHello; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -9,7 +9,7 @@ import net.minecraft.server.network.NetHandlerLoginServer; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; diff --git a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 82% rename from legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java index a407f474..3a82fe05 100644 --- a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v12_2.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v12_2.forge.forwarding; import com.mojang.authlib.GameProfile; @@ -11,14 +11,13 @@ import net.minecraft.server.network.NetHandlerLoginServer; import net.minecraft.util.text.ITextComponent; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; @AConstraint( mappings = Mappings.SEARGE, @@ -30,19 +29,8 @@ public abstract class ServerLoginPacketListenerImplMixin @Shadow @Final public NetworkManager networkManager; @Shadow private GameProfile loginGameProfile; @Shadow private NetHandlerLoginServer.LoginState currentLoginState; - @Unique private int pcf$velocityLoginMessageId = -1; // spotless:on - @Override - public int bridge$velocityLoginMessageId() { - return this.pcf$velocityLoginMessageId; - } - - @Override - public void bridge$setVelocityLoginMessageId(final int id) { - this.pcf$velocityLoginMessageId = id; - } - @Override public @NonNull ConnectionBridge bridge$connection() { return (ConnectionBridge) this.networkManager; diff --git a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ConnectionMixin.java b/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ConnectionMixin.java deleted file mode 100644 index 1e73dd28..00000000 --- a/legacy/v12_2/src/forge/java/org/adde0109/pcf/mixin/v12_2/forge/forwarding/modern/ConnectionMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.adde0109.pcf.mixin.v12_2.forge.forwarding.modern; - -import dev.neuralnexus.taterapi.meta.Mappings; -import dev.neuralnexus.taterapi.meta.anno.AConstraint; -import dev.neuralnexus.taterapi.meta.anno.Versions; -import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; - -import net.minecraft.network.INetHandler; -import net.minecraft.network.NetworkManager; - -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -@AConstraint( - mappings = Mappings.SEARGE, - version = @Versions(min = MinecraftVersion.V7, max = MinecraftVersion.V12_2)) -@Mixin(NetworkManager.class) -public abstract class ConnectionMixin implements ConnectionBridge { - // spotless:off - @Shadow private SocketAddress socketAddress; - @Shadow public abstract INetHandler shadow$getNetHandler(); - // spotless:on - - @Override - public @NonNull InetSocketAddress bridge$address() { - return (InetSocketAddress) this.socketAddress; - } - - @Override - public void bridge$address(final @NonNull InetSocketAddress address) { - this.socketAddress = address; - } - - @Override - public @Nullable Object bridge$getPacketListener() { - return this.shadow$getNetHandler(); - } -} diff --git a/legacy/v12_2/src/forge/resources/pcf.mixins.v12_2.forge.json b/legacy/v12_2/src/forge/resources/pcf.mixins.v12_2.forge.json index d6446391..996f37cd 100644 --- a/legacy/v12_2/src/forge/resources/pcf.mixins.v12_2.forge.json +++ b/legacy/v12_2/src/forge/resources/pcf.mixins.v12_2.forge.json @@ -8,9 +8,8 @@ "plugin": "org.adde0109.pcf.mixin.plugin.PCFMixinPlugin", "package": "org.adde0109.pcf.mixin", "server": [ - "v12_2.forge.forwarding.modern.ConnectionMixin", - "v12_2.forge.forwarding.modern.ServerLoginPacketListenerImplHelloMixin", - "v12_2.forge.forwarding.modern.ServerLoginPacketListenerImplMixin", - "v12_2.forge.forwarding.modern.ServerLoginPacketListenerImplMixin$SLPLIMixin_12" + "v12_2.forge.forwarding.ServerLoginPacketListenerImplMixin", + "v12_2.forge.forwarding.ServerLoginPacketListenerImplMixin$SLPLIMixin_12", + "v12_2.forge.forwarding.ServerLoginPacketListenerImplHelloMixin" ] } diff --git a/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/MessageSerializerMixin.java b/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/MessageSerializerMixin.java similarity index 93% rename from legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/MessageSerializerMixin.java rename to legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/MessageSerializerMixin.java index 0b2597e4..54068f17 100644 --- a/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/MessageSerializerMixin.java +++ b/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/MessageSerializerMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v7_10.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v7_10.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; diff --git a/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 86% rename from legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/ServerLoginPacketListenerImplMixin.java index 536c7612..7ac1d8f4 100644 --- a/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/legacy/v7_10/src/forge/java/org/adde0109/pcf/mixin/v7_10/forge/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v7_10.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v7_10.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -8,7 +8,7 @@ import net.minecraft.server.network.NetHandlerLoginServer; import net.minecraft.util.IChatComponent; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.Final; @@ -28,7 +28,8 @@ public abstract class ServerLoginPacketListenerImplMixin @Override public void bridge$disconnect(final @NonNull Object reason) { - this.shadow$closeConnection(((IChatComponent) reason).getFormattedText()); + // Note: IChatComponent.getFormattedText() is client-only + this.shadow$closeConnection(((IChatComponent) reason).getUnformattedTextForChat()); } @Override diff --git a/legacy/v7_10/src/forge/resources/pcf.mixins.v7_10.forge.json b/legacy/v7_10/src/forge/resources/pcf.mixins.v7_10.forge.json index 8d99e8c9..9a947598 100644 --- a/legacy/v7_10/src/forge/resources/pcf.mixins.v7_10.forge.json +++ b/legacy/v7_10/src/forge/resources/pcf.mixins.v7_10.forge.json @@ -8,7 +8,7 @@ "plugin": "org.adde0109.pcf.mixin.plugin.PCFMixinPlugin", "package": "org.adde0109.pcf.mixin", "server": [ - "v7_10.forge.forwarding.modern.MessageSerializerMixin", - "v7_10.forge.forwarding.modern.ServerLoginPacketListenerImplMixin" + "v7_10.forge.forwarding.MessageSerializerMixin", + "v7_10.forge.forwarding.ServerLoginPacketListenerImplMixin" ] } diff --git a/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ConnectionMixin.java b/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ConnectionMixin.java new file mode 100644 index 00000000..3f86b0c4 --- /dev/null +++ b/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ConnectionMixin.java @@ -0,0 +1,37 @@ +package org.adde0109.pcf.mixin.v13_2.forge.forwarding; + +import dev.neuralnexus.taterapi.meta.Mappings; +import dev.neuralnexus.taterapi.meta.anno.AConstraint; +import dev.neuralnexus.taterapi.meta.anno.Versions; +import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; +import dev.neuralnexus.taterapi.network.Protocol; + +import io.netty.util.AttributeKey; + +import net.minecraft.network.EnumConnectionState; +import net.minecraft.network.NetworkManager; + +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@AConstraint( + mappings = Mappings.SEARGE, + version = @Versions(min = MinecraftVersion.V7, max = MinecraftVersion.V13_2)) +@Mixin(NetworkManager.class) +public abstract class ConnectionMixin implements ConnectionBridge { + // spotless:off + @Shadow @Final public static AttributeKey PROTOCOL_ATTRIBUTE_KEY; + // spotless:on + + @Override + public Protocol bridge$protocol() { + final Object listener = this.bridge$getPacketListener(); + if (listener == null) { + return null; + } + return Protocol.fromLegacyId( + this.bridge$channel().attr(PROTOCOL_ATTRIBUTE_KEY).get().getId()); + } +} diff --git a/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 81% rename from modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java index 4638354b..60ca66af 100644 --- a/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/modern/v13_2/src/forge/java/org/adde0109/pcf/mixin/v13_2/forge/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,6 +1,6 @@ -package org.adde0109.pcf.mixin.v13_2.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v13_2.forge.forwarding; -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleHello; +import static org.adde0109.pcf.forwarding.Forwarding.handleHello; import com.mojang.authlib.GameProfile; @@ -13,8 +13,8 @@ import net.minecraft.network.NetworkManager; import net.minecraft.util.text.ITextComponent; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -22,7 +22,6 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -39,7 +38,6 @@ public abstract class ServerLoginPacketListenerImplMixin @Shadow private NetHandlerLoginServer.LoginState currentLoginState; @Shadow @Final private static Logger LOGGER; @Shadow public abstract void shadow$disconnect(ITextComponent reason); - @Unique private int pcf$velocityLoginMessageId = -1; // spotless:on // spotless:off @@ -50,16 +48,6 @@ private void onHandleHello(final @NonNull CallbackInfo ci) { handleHello(this, ci); } - @Override - public int bridge$velocityLoginMessageId() { - return this.pcf$velocityLoginMessageId; - } - - @Override - public void bridge$setVelocityLoginMessageId(final int id) { - this.pcf$velocityLoginMessageId = id; - } - @Override public @NonNull ConnectionBridge bridge$connection() { return (ConnectionBridge) this.networkManager; diff --git a/modern/v13_2/src/forge/resources/pcf.mixins.v13_2.forge.json b/modern/v13_2/src/forge/resources/pcf.mixins.v13_2.forge.json index dbf496cb..8329255a 100644 --- a/modern/v13_2/src/forge/resources/pcf.mixins.v13_2.forge.json +++ b/modern/v13_2/src/forge/resources/pcf.mixins.v13_2.forge.json @@ -9,6 +9,7 @@ "package": "org.adde0109.pcf.mixin", "server": [ "v13_2.forge.crossstitch.CommandsPacketMixin", - "v13_2.forge.forwarding.modern.ServerLoginPacketListenerImplMixin" + "v13_2.forge.forwarding.ConnectionMixin", + "v13_2.forge.forwarding.ServerLoginPacketListenerImplMixin" ] } diff --git a/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ConnectionMixin.java b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ConnectionMixin.java similarity index 56% rename from modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ConnectionMixin.java rename to modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ConnectionMixin.java index f4263bc3..9fe073d3 100644 --- a/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ConnectionMixin.java +++ b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ConnectionMixin.java @@ -1,16 +1,21 @@ -package org.adde0109.pcf.mixin.v16_5.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v16_5.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; import dev.neuralnexus.taterapi.meta.anno.Versions; import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; +import dev.neuralnexus.taterapi.network.Protocol; + +import io.netty.util.AttributeKey; import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.PacketListener; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -19,12 +24,15 @@ @AConstraint( mappings = Mappings.SEARGE, - version = @Versions(min = MinecraftVersion.V13, max = MinecraftVersion.V16_5)) + version = @Versions(min = MinecraftVersion.V7, max = MinecraftVersion.V16_5)) @Mixin(Connection.class) public abstract class ConnectionMixin implements ConnectionBridge { // spotless:off @Shadow private SocketAddress address; @Shadow public abstract PacketListener shadow$getPacketListener(); + + @AConstraint(version = @Versions(min = MinecraftVersion.V14, max = MinecraftVersion.V16_5)) + @Shadow @Final public static AttributeKey ATTRIBUTE_PROTOCOL; // spotless:on @Override @@ -41,4 +49,14 @@ public abstract class ConnectionMixin implements ConnectionBridge { public @Nullable Object bridge$getPacketListener() { return this.shadow$getPacketListener(); } + + @AConstraint(version = @Versions(min = MinecraftVersion.V14, max = MinecraftVersion.V16_5)) + @Override + public Protocol bridge$protocol() { + final Object listener = this.bridge$getPacketListener(); + if (listener == null) { + return null; + } + return Protocol.fromLegacyId(this.bridge$channel().attr(ATTRIBUTE_PROTOCOL).get().getId()); + } } diff --git a/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java new file mode 100644 index 00000000..b63946f2 --- /dev/null +++ b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplHelloMixin.java @@ -0,0 +1,53 @@ +package org.adde0109.pcf.mixin.v16_5.forge.forwarding; + +import static org.adde0109.pcf.forwarding.Forwarding.handleHello; + +import dev.neuralnexus.taterapi.meta.Mappings; +import dev.neuralnexus.taterapi.meta.anno.AConstraint; +import dev.neuralnexus.taterapi.meta.anno.Versions; +import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; +import dev.neuralnexus.taterapi.meta.enums.Platform; + +import net.minecraft.server.network.ServerLoginPacketListenerImpl; + +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; +import org.apache.commons.lang3.Validate; +import org.jspecify.annotations.NonNull; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@AConstraint( + mappings = Mappings.SEARGE, + version = @Versions(min = MinecraftVersion.V14, max = MinecraftVersion.V16_5)) +@Mixin(ServerLoginPacketListenerImpl.class) +public abstract class ServerLoginPacketListenerImplHelloMixin + implements ServerLoginPacketListenerBridge { + // spotless:off + @Shadow private ServerLoginPacketListenerImpl.State state; + + @AConstraint( + platform = {Platform.ARCLIGHT, Platform.CATSERVER, Platform.MAGMA, Platform.MOHIST}, invert = true) + @Inject(method = "handleHello", cancellable = true, at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, ordinal = 1, + target = "Lnet/minecraft/server/network/ServerLoginPacketListenerImpl;state:Lnet/minecraft/server/network/ServerLoginPacketListenerImpl$State;")) + // spotless:on + private void onHandleHello(final @NonNull CallbackInfo ci) { + handleHello(this, ci); + } + + /** + * Arclight - Overwrites the method
+ * CatServer, Magma, Mohist - Patches alter the method in an incompatible manner + */ + @AConstraint( + platform = {Platform.ARCLIGHT, Platform.CATSERVER, Platform.MAGMA, Platform.MOHIST}) + @Inject(method = "handleHello", cancellable = true, at = @At(value = "HEAD")) + private void onHandleHelloHybrid(final @NonNull CallbackInfo ci) { + Validate.validState( + this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet"); + handleHello(this, ci); + } +} diff --git a/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 51% rename from modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplMixin.java index 1f94b4c1..98c1d7e9 100644 --- a/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/modern/v16_5/src/forge/java/org/adde0109/pcf/mixin/v16_5/forge/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,6 +1,4 @@ -package org.adde0109.pcf.mixin.v16_5.forge.forwarding.modern; - -import static org.adde0109.pcf.forwarding.modern.ModernForwarding.handleHello; +package org.adde0109.pcf.mixin.v16_5.forge.forwarding; import com.mojang.authlib.GameProfile; @@ -8,26 +6,19 @@ import dev.neuralnexus.taterapi.meta.anno.AConstraint; import dev.neuralnexus.taterapi.meta.anno.Versions; import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; -import dev.neuralnexus.taterapi.meta.enums.Platform; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; -import org.apache.commons.lang3.Validate; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @AConstraint( mappings = Mappings.SEARGE, @@ -41,41 +32,7 @@ public abstract class ServerLoginPacketListenerImplMixin @Shadow private ServerLoginPacketListenerImpl.State state; @Shadow @Final private static Logger LOGGER; @Shadow public abstract void shadow$disconnect(Component reason); - @Unique private int pcf$velocityLoginMessageId = -1; - // spotless:on - - // spotless:off - @AConstraint( - platform = {Platform.ARCLIGHT, Platform.CATSERVER, Platform.MAGMA, Platform.MOHIST}, invert = true) - @Inject(method = "handleHello", cancellable = true, at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, ordinal = 1, - target = "Lnet/minecraft/server/network/ServerLoginPacketListenerImpl;state:Lnet/minecraft/server/network/ServerLoginPacketListenerImpl$State;")) // spotless:on - private void onHandleHello(final @NonNull CallbackInfo ci) { - handleHello(this, ci); - } - - /** - * Arclight - Overwrites the method
- * CatServer, Magma, Mohist - Patches alter the method in an incompatible manner - */ - @AConstraint( - platform = {Platform.ARCLIGHT, Platform.CATSERVER, Platform.MAGMA, Platform.MOHIST}) - @Inject(method = "handleHello", cancellable = true, at = @At(value = "HEAD")) - private void onHandleHelloHybrid(final @NonNull CallbackInfo ci) { - Validate.validState( - this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet"); - handleHello(this, ci); - } - - @Override - public int bridge$velocityLoginMessageId() { - return this.pcf$velocityLoginMessageId; - } - - @Override - public void bridge$setVelocityLoginMessageId(final int id) { - this.pcf$velocityLoginMessageId = id; - } @Override public @NonNull ConnectionBridge bridge$connection() { diff --git a/modern/v16_5/src/forge/resources/pcf.mixins.v16_5.forge.json b/modern/v16_5/src/forge/resources/pcf.mixins.v16_5.forge.json index 8250d302..b61ea358 100644 --- a/modern/v16_5/src/forge/resources/pcf.mixins.v16_5.forge.json +++ b/modern/v16_5/src/forge/resources/pcf.mixins.v16_5.forge.json @@ -11,7 +11,8 @@ "v16_5.forge.crossstitch.ArgumentTypesAccessor", "v16_5.forge.crossstitch.ArgumentTypesEntryMixin", "v16_5.forge.crossstitch.CommandsPacketMixin", - "v16_5.forge.forwarding.modern.ConnectionMixin", - "v16_5.forge.forwarding.modern.ServerLoginPacketListenerImplMixin" + "v16_5.forge.forwarding.ConnectionMixin", + "v16_5.forge.forwarding.ServerLoginPacketListenerImplMixin", + "v16_5.forge.forwarding.ServerLoginPacketListenerImplHelloMixin" ] } diff --git a/modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java b/modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java similarity index 89% rename from modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java rename to modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java index 77ca441c..ce1441e0 100644 --- a/modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java +++ b/modern/v17_1/src/forge/java/org/adde0109/pcf/mixin/v17_1/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v17_1.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v17_1.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -7,7 +7,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.NonNull; import org.spongepowered.asm.mixin.Final; diff --git a/modern/v17_1/src/forge/resources/pcf.mixins.v17_1.forge.json b/modern/v17_1/src/forge/resources/pcf.mixins.v17_1.forge.json index 5c783ef5..2fbab5ad 100644 --- a/modern/v17_1/src/forge/resources/pcf.mixins.v17_1.forge.json +++ b/modern/v17_1/src/forge/resources/pcf.mixins.v17_1.forge.json @@ -11,6 +11,6 @@ "v17_1.forge.crossstitch.ArgumentTypesAccessor", "v17_1.forge.crossstitch.ArgumentTypesEntryMixin", "v17_1.forge.crossstitch.CommandsPacketMixin", - "v17_1.forge.forwarding.modern.ServerLoginPacketListenerImplLoggerMixin" + "v17_1.forge.forwarding.ServerLoginPacketListenerImplLoggerMixin" ] } diff --git a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ConnectionMixin.java b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ConnectionMixin.java new file mode 100644 index 00000000..a15645ee --- /dev/null +++ b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ConnectionMixin.java @@ -0,0 +1,36 @@ +package org.adde0109.pcf.mixin.v19_2.forge.forwarding; + +import dev.neuralnexus.taterapi.meta.Mappings; +import dev.neuralnexus.taterapi.meta.anno.AConstraint; +import dev.neuralnexus.taterapi.meta.anno.Versions; +import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; +import dev.neuralnexus.taterapi.network.Protocol; + +import io.netty.util.AttributeKey; + +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; + +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@AConstraint( + mappings = Mappings.SEARGE, + version = @Versions(min = MinecraftVersion.V17, max = MinecraftVersion.V20_1)) +@Mixin(Connection.class) +public abstract class ConnectionMixin implements ConnectionBridge { + // spotless:off + @Shadow @Final public static AttributeKey ATTRIBUTE_PROTOCOL; + // spotless:on + + @Override + public Protocol bridge$protocol() { + final Object listener = this.bridge$getPacketListener(); + if (listener == null) { + return null; + } + return Protocol.fromLegacyId(this.bridge$channel().attr(ATTRIBUTE_PROTOCOL).get().getId()); + } +} diff --git a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplStartClientVerificationMixin.java b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ServerLoginPacketListenerImplStartClientVerificationMixin.java similarity index 90% rename from modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplStartClientVerificationMixin.java rename to modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ServerLoginPacketListenerImplStartClientVerificationMixin.java index b8aa4ad7..b00b5c3a 100644 --- a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplStartClientVerificationMixin.java +++ b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/ServerLoginPacketListenerImplStartClientVerificationMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v19_2.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v19_2.forge.forwarding; import com.mojang.authlib.GameProfile; @@ -9,7 +9,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; diff --git a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV1Mixin.java b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV1Mixin.java index 088db0f2..3652bc8b 100644 --- a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV1Mixin.java +++ b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV1Mixin.java @@ -10,7 +10,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV2Mixin.java b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV2Mixin.java index 24b08bfc..13f67cdd 100644 --- a/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV2Mixin.java +++ b/modern/v19_2/src/forge/java/org/adde0109/pcf/mixin/v19_2/forge/forwarding/modern/ServerLoginPacketListenerImplKeyV2Mixin.java @@ -13,7 +13,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; import net.minecraft.util.SignatureValidator; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/modern/v19_2/src/forge/resources/pcf.mixins.v19_2.forge.json b/modern/v19_2/src/forge/resources/pcf.mixins.v19_2.forge.json index 69f323bf..4bbd6f8a 100644 --- a/modern/v19_2/src/forge/resources/pcf.mixins.v19_2.forge.json +++ b/modern/v19_2/src/forge/resources/pcf.mixins.v19_2.forge.json @@ -8,11 +8,12 @@ "plugin": "org.adde0109.pcf.mixin.plugin.PCFMixinPlugin", "package": "org.adde0109.pcf.mixin", "server": [ + "v19_2.forge.forwarding.ConnectionMixin", + "v19_2.forge.forwarding.ServerLoginPacketListenerImplStartClientVerificationMixin", "v19_2.forge.forwarding.modern.LastSeenMessagesValidatorMixin", "v19_2.forge.forwarding.modern.PlayerChatMessageMixin", "v19_2.forge.forwarding.modern.ServerLoginPacketListenerImplKeyV1Mixin", "v19_2.forge.forwarding.modern.ServerLoginPacketListenerImplKeyV2Mixin", - "v19_2.forge.forwarding.modern.ServerLoginPacketListenerImplStartClientVerificationMixin", "v19_2.forge.forwarding.modern.SignedMessageChainMixin" ] } diff --git a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ConnectionMixin.java b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ConnectionMixin.java similarity index 69% rename from modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ConnectionMixin.java rename to modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ConnectionMixin.java index b286edd2..e4d2e8bb 100644 --- a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ConnectionMixin.java +++ b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ConnectionMixin.java @@ -1,14 +1,15 @@ -package org.adde0109.pcf.mixin.v20_4.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v20_4.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; import dev.neuralnexus.taterapi.meta.anno.Versions; import dev.neuralnexus.taterapi.meta.enums.MinecraftVersion; +import dev.neuralnexus.taterapi.network.Protocol; import net.minecraft.network.Connection; import net.minecraft.network.PacketListener; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -41,4 +42,16 @@ public abstract class ConnectionMixin implements ConnectionBridge { public @Nullable Object bridge$getPacketListener() { return this.shadow$getPacketListener(); } + + @AConstraint( + mappings = Mappings.SEARGE, + version = @Versions(min = MinecraftVersion.V20_2, max = MinecraftVersion.V20_4)) + @Override + public Protocol bridge$protocol() { + final PacketListener listener = this.shadow$getPacketListener(); + if (listener == null) { + return null; + } + return Protocol.fromId(listener.protocol().id()); + } } diff --git a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java similarity index 88% rename from modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java rename to modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java index b62f2076..3cf5a308 100644 --- a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplLoggerMixin.java +++ b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplLoggerMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v20_4.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v20_4.forge.forwarding; import dev.neuralnexus.taterapi.meta.Mappings; import dev.neuralnexus.taterapi.meta.anno.AConstraint; @@ -7,7 +7,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; diff --git a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplMixin.java similarity index 78% rename from modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java rename to modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplMixin.java index ceee182b..7c4986e1 100644 --- a/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/modern/ServerLoginPacketListenerImplMixin.java +++ b/modern/v20_4/src/forge/java/org/adde0109/pcf/mixin/v20_4/forge/forwarding/ServerLoginPacketListenerImplMixin.java @@ -1,4 +1,4 @@ -package org.adde0109.pcf.mixin.v20_4.forge.forwarding.modern; +package org.adde0109.pcf.mixin.v20_4.forge.forwarding; import com.mojang.authlib.GameProfile; @@ -11,14 +11,13 @@ import net.minecraft.network.chat.Component; import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.adde0109.pcf.forwarding.modern.ConnectionBridge; -import org.adde0109.pcf.forwarding.modern.ServerLoginPacketListenerBridge; +import org.adde0109.pcf.forwarding.ConnectionBridge; +import org.adde0109.pcf.forwarding.ServerLoginPacketListenerBridge; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; @AConstraint(mappings = Mappings.SEARGE, version = @Versions(min = MinecraftVersion.V17)) @Mixin(ServerLoginPacketListenerImpl.class) @@ -33,20 +32,8 @@ public abstract class ServerLoginPacketListenerImplMixin @AConstraint(version = @Versions(min = MinecraftVersion.V20_2)) @Shadow private @Nullable GameProfile authenticatedProfile; - - @Unique private int pcf$velocityLoginMessageId = -1; // spotless:on - @Override - public int bridge$velocityLoginMessageId() { - return this.pcf$velocityLoginMessageId; - } - - @Override - public void bridge$setVelocityLoginMessageId(final int id) { - this.pcf$velocityLoginMessageId = id; - } - @Override public @NonNull ConnectionBridge bridge$connection() { return (ConnectionBridge) this.connection; diff --git a/modern/v20_4/src/forge/resources/pcf.mixins.v20_4.forge.json b/modern/v20_4/src/forge/resources/pcf.mixins.v20_4.forge.json index f444e6e2..ab27487d 100644 --- a/modern/v20_4/src/forge/resources/pcf.mixins.v20_4.forge.json +++ b/modern/v20_4/src/forge/resources/pcf.mixins.v20_4.forge.json @@ -10,8 +10,8 @@ "server": [ "v20_4.forge.crossstitch.ArgumentNodeStubMixin", "v20_4.forge.crossstitch.ArgumentTypeInfoMixin", - "v20_4.forge.forwarding.modern.ConnectionMixin", - "v20_4.forge.forwarding.modern.ServerLoginPacketListenerImplLoggerMixin", - "v20_4.forge.forwarding.modern.ServerLoginPacketListenerImplMixin" + "v20_4.forge.forwarding.ConnectionMixin", + "v20_4.forge.forwarding.ServerLoginPacketListenerImplLoggerMixin", + "v20_4.forge.forwarding.ServerLoginPacketListenerImplMixin" ] } diff --git a/test/HeadlessMC/templates/server/config/proxy-compatible-forge.cfg b/test/HeadlessMC/templates/server/config/proxy-compatible-forge.cfg new file mode 100644 index 00000000..d1fac88f --- /dev/null +++ b/test/HeadlessMC/templates/server/config/proxy-compatible-forge.cfg @@ -0,0 +1,29 @@ +# Configuration file + +advanced { + S:modernForwardingVersion=NO_OVERRIDE +} + + +debug { + S:disabledMixins < + > + B:enabled=true +} + + +forwarding { + S:approvedProxyHosts < + 127.0.0.1 + > + B:enabled=true + S:mode=MODERN + S:secret=testtesttest +} + + +general { + D:version=2.0 +} + + diff --git a/test/HeadlessMC/templates/server/ops.json b/test/HeadlessMC/templates/server/ops.json index 45975348..8b00228c 100644 --- a/test/HeadlessMC/templates/server/ops.json +++ b/test/HeadlessMC/templates/server/ops.json @@ -4,5 +4,11 @@ "name": "dev", "level": 4, "bypassesPlayerLimit": false + }, + { + "uuid": "3cf69d1b-a45a-4c19-9ff7-ac1e3bafec6b", + "name": "metalcatian", + "level": 4, + "bypassesPlayerLimit": false } ] \ No newline at end of file diff --git a/test/HeadlessMC/templates/velocity/bungeeguard/velocity.toml b/test/HeadlessMC/templates/velocity/bungeeguard/velocity.toml new file mode 100644 index 00000000..ab9f1538 --- /dev/null +++ b/test/HeadlessMC/templates/velocity/bungeeguard/velocity.toml @@ -0,0 +1,189 @@ +# Config version. Do not change this +config-version = "2.7" + +# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. +bind = "0.0.0.0:25579" + +# What should be the MOTD? This gets displayed when the player adds your server to +# their server list. Only MiniMessage format is accepted. +motd = "<#09add3>A BungeeGuard Velocity Server" + +# What should we display for the maximum number of players? (Velocity does not support a cap +# on the number of players online.) +show-max-players = 500 + +# Should we authenticate players with Mojang? By default, this is on. +online-mode = false + +# Should the proxy enforce the new public key security standard? By default, this is on. +force-key-authentication = true + +# If client's ISP/AS sent from this proxy is different from the one from Mojang's +# authentication server, the player is kicked. This disallows some VPN and proxy +# connections but is a weak form of protection. +prevent-client-proxy-connections = false + +# Should we forward IP addresses and other data to backend servers? +# Available options: +# - "none": No forwarding will be done. All players will appear to be connecting +# from the proxy and will have offline-mode UUIDs. +# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this +# if you run servers using Minecraft 1.12 or lower. +# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard +# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are +# unable to implement network level firewalling (on a shared host). +# - "modern": Forward player IPs and UUIDs as part of the login process using +# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher. +player-info-forwarding-mode = "BUNGEEGUARD" + +# If you are using modern or BungeeGuard IP forwarding, configure a file that contains a unique secret here. +# The file is expected to be UTF-8 encoded and not empty. +forwarding-secret-file = "forwarding.secret" + +# Announce whether or not your server supports Forge. If you run a modded server, we +# suggest turning this on. +# +# If your network runs one modpack consistently, consider using ping-passthrough = "mods" +# instead for a nicer display in the server list. +announce-forge = true + +# If enabled (default is false) and the proxy is in online mode, Velocity will kick +# any existing player who is online if a duplicate connection attempt is made. +kick-existing-players = false + +# Should Velocity pass server list ping requests to a backend server? +# Available options: +# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png +# will determine the initial server list ping response. +# - "mods": Passes only the mod list from your backend server into the response. +# The first server in your try list (or forced host) with a mod list will be +# used. If no backend servers can be contacted, Velocity won't display any +# mod information. +# - "description": Uses the description and mod list from the backend server. The first +# server in the try (or forced host) list that responds is used for the +# description and mod list. +# - "all": Uses the backend server's response as the proxy response. The Velocity +# configuration is used if no servers could be contacted. +ping-passthrough = "MODS" + +# If enabled (default is false), then a sample of the online players on the proxy will be visible +# when hovering over the player count in the server list. +# This doesn't have any effect when ping passthrough is set to either "description" or "all". +sample-players-in-ping = false + +# If not enabled (default is true) player IP addresses will be replaced by in logs +enable-player-address-logging = true + +[packet-limiter] +interval = 7 +packets-per-second = 500 +bytes-per-second = -1 + +[servers] +# Configure your servers here. Each key represents the server's name, and the value +# represents the IP address of the server to connect to. +server1 = "127.0.0.1:25565" +server2 = "127.0.0.1:25565" + +# In what order we should try servers when a player logs in or is kicked from a server. +try = [ + "server1" +] + +[forced-hosts] +# Configure your forced hosts here. +"server1.example.com" = [ + "server1" +] + +[advanced] +# How large a Minecraft packet has to be before we compress it. Setting this to zero will +# compress all packets, and setting it to -1 will disable compression entirely. +compression-threshold = 256 + +# How much compression should be done (from 0-9). The default is -1, which uses the +# default level of 6. +compression-level = -1 + +# How fast (in milliseconds) are clients allowed to connect after the last connection? By +# default, this is three seconds. Disable this by setting this to 0. +login-ratelimit = 3000 + +# Specify a custom timeout for connection timeouts here. The default is five seconds. +connection-timeout = 5000 + +# Specify a read timeout for connections here. The default is 30 seconds. +read-timeout = 180000 + +# Enables compatibility with HAProxy's PROXY protocol. If you don't know what this is for, then +# don't enable it. +haproxy-protocol = false + +# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux. +tcp-fast-open = false + +# Enables BungeeCord plugin messaging channel support on Velocity. +bungee-plugin-message-channel = true + +# Shows ping requests to the proxy from clients. +show-ping-requests = true + +# By default, Velocity will attempt to gracefully handle situations where the user unexpectedly +# loses connection to the server without an explicit disconnect message by attempting to fall the +# user back, except in the case of read timeouts. BungeeCord will disconnect the user instead. You +# can disable this setting to use the BungeeCord behavior. +failover-on-unexpected-server-disconnect = true + +# Declares the proxy commands to 1.13+ clients. +announce-proxy-commands = true + +# Enables the logging of commands +log-command-executions = true + +# Enables logging of player connections when connecting to the proxy, switching servers +# and disconnecting from the proxy. +log-player-connections = true + +# Allows players transferred from other hosts via the +# Transfer packet (Minecraft 1.20.5) to be received. +accepts-transfers = false + +# Enables support for SO_REUSEPORT. This may help the proxy scale better on multicore systems +# with a lot of incoming connections, and provide better CPU utilization than the existing +# strategy of having a single thread accepting connections and distributing them to worker +# threads. Disabled by default. Requires Linux or macOS. +enable-reuse-port = false + +# How fast (in milliseconds) are clients allowed to send commands after the last command +# By default this is 50ms (20 commands per second) +command-rate-limit = 50 + +# Should we forward commands to the backend upon being rate limited? +# This will forward the command to the server instead of processing it on the proxy. +# Since most server implementations have a rate limit, this will prevent the player +# from being able to send excessive commands to the server. +forward-commands-if-rate-limited = true + +# How many commands are allowed to be sent after the rate limit is hit before the player is kicked? +# Setting this to 0 or lower will disable this feature. +kick-after-rate-limited-commands = 0 + +# How fast (in milliseconds) are clients allowed to send tab completions after the last tab completion +tab-complete-rate-limit = 10 + +# How many tab completions are allowed to be sent after the rate limit is hit before the player is kicked? +# Setting this to 0 or lower will disable this feature. +kick-after-rate-limited-tab-completes = 0 + +[query] +# Whether to enable responding to GameSpy 4 query responses or not. +enabled = true + +# If query is enabled, on what port should the query protocol listen on? +port = 25579 + +# This is the map name that is reported to the query services. +map = "Velocity" + +# Whether plugins should be shown in query response by default or not +show-plugins = false diff --git a/test/HeadlessMC/templates/velocity/forwarding.secret b/test/HeadlessMC/templates/velocity/common/forwarding.secret similarity index 100% rename from test/HeadlessMC/templates/velocity/forwarding.secret rename to test/HeadlessMC/templates/velocity/common/forwarding.secret diff --git a/test/HeadlessMC/templates/velocity/plugins/bStats/config.txt b/test/HeadlessMC/templates/velocity/common/plugins/bStats/config.txt similarity index 100% rename from test/HeadlessMC/templates/velocity/plugins/bStats/config.txt rename to test/HeadlessMC/templates/velocity/common/plugins/bStats/config.txt diff --git a/test/HeadlessMC/templates/velocity/legacy/velocity.toml b/test/HeadlessMC/templates/velocity/legacy/velocity.toml new file mode 100644 index 00000000..3c186cd8 --- /dev/null +++ b/test/HeadlessMC/templates/velocity/legacy/velocity.toml @@ -0,0 +1,189 @@ +# Config version. Do not change this +config-version = "2.7" + +# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. +bind = "0.0.0.0:25578" + +# What should be the MOTD? This gets displayed when the player adds your server to +# their server list. Only MiniMessage format is accepted. +motd = "<#09add3>A Legacy Velocity Server" + +# What should we display for the maximum number of players? (Velocity does not support a cap +# on the number of players online.) +show-max-players = 500 + +# Should we authenticate players with Mojang? By default, this is on. +online-mode = false + +# Should the proxy enforce the new public key security standard? By default, this is on. +force-key-authentication = true + +# If client's ISP/AS sent from this proxy is different from the one from Mojang's +# authentication server, the player is kicked. This disallows some VPN and proxy +# connections but is a weak form of protection. +prevent-client-proxy-connections = false + +# Should we forward IP addresses and other data to backend servers? +# Available options: +# - "none": No forwarding will be done. All players will appear to be connecting +# from the proxy and will have offline-mode UUIDs. +# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this +# if you run servers using Minecraft 1.12 or lower. +# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard +# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are +# unable to implement network level firewalling (on a shared host). +# - "modern": Forward player IPs and UUIDs as part of the login process using +# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher. +player-info-forwarding-mode = "LEGACY" + +# If you are using modern or BungeeGuard IP forwarding, configure a file that contains a unique secret here. +# The file is expected to be UTF-8 encoded and not empty. +forwarding-secret-file = "forwarding.secret" + +# Announce whether or not your server supports Forge. If you run a modded server, we +# suggest turning this on. +# +# If your network runs one modpack consistently, consider using ping-passthrough = "mods" +# instead for a nicer display in the server list. +announce-forge = true + +# If enabled (default is false) and the proxy is in online mode, Velocity will kick +# any existing player who is online if a duplicate connection attempt is made. +kick-existing-players = false + +# Should Velocity pass server list ping requests to a backend server? +# Available options: +# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png +# will determine the initial server list ping response. +# - "mods": Passes only the mod list from your backend server into the response. +# The first server in your try list (or forced host) with a mod list will be +# used. If no backend servers can be contacted, Velocity won't display any +# mod information. +# - "description": Uses the description and mod list from the backend server. The first +# server in the try (or forced host) list that responds is used for the +# description and mod list. +# - "all": Uses the backend server's response as the proxy response. The Velocity +# configuration is used if no servers could be contacted. +ping-passthrough = "MODS" + +# If enabled (default is false), then a sample of the online players on the proxy will be visible +# when hovering over the player count in the server list. +# This doesn't have any effect when ping passthrough is set to either "description" or "all". +sample-players-in-ping = false + +# If not enabled (default is true) player IP addresses will be replaced by in logs +enable-player-address-logging = true + +[packet-limiter] +interval = 7 +packets-per-second = 500 +bytes-per-second = -1 + +[servers] +# Configure your servers here. Each key represents the server's name, and the value +# represents the IP address of the server to connect to. +server1 = "127.0.0.1:25565" +server2 = "127.0.0.1:25565" + +# In what order we should try servers when a player logs in or is kicked from a server. +try = [ + "server1" +] + +[forced-hosts] +# Configure your forced hosts here. +"server1.example.com" = [ + "server1" +] + +[advanced] +# How large a Minecraft packet has to be before we compress it. Setting this to zero will +# compress all packets, and setting it to -1 will disable compression entirely. +compression-threshold = 256 + +# How much compression should be done (from 0-9). The default is -1, which uses the +# default level of 6. +compression-level = -1 + +# How fast (in milliseconds) are clients allowed to connect after the last connection? By +# default, this is three seconds. Disable this by setting this to 0. +login-ratelimit = 3000 + +# Specify a custom timeout for connection timeouts here. The default is five seconds. +connection-timeout = 5000 + +# Specify a read timeout for connections here. The default is 30 seconds. +read-timeout = 180000 + +# Enables compatibility with HAProxy's PROXY protocol. If you don't know what this is for, then +# don't enable it. +haproxy-protocol = false + +# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux. +tcp-fast-open = false + +# Enables BungeeCord plugin messaging channel support on Velocity. +bungee-plugin-message-channel = true + +# Shows ping requests to the proxy from clients. +show-ping-requests = true + +# By default, Velocity will attempt to gracefully handle situations where the user unexpectedly +# loses connection to the server without an explicit disconnect message by attempting to fall the +# user back, except in the case of read timeouts. BungeeCord will disconnect the user instead. You +# can disable this setting to use the BungeeCord behavior. +failover-on-unexpected-server-disconnect = true + +# Declares the proxy commands to 1.13+ clients. +announce-proxy-commands = true + +# Enables the logging of commands +log-command-executions = true + +# Enables logging of player connections when connecting to the proxy, switching servers +# and disconnecting from the proxy. +log-player-connections = true + +# Allows players transferred from other hosts via the +# Transfer packet (Minecraft 1.20.5) to be received. +accepts-transfers = false + +# Enables support for SO_REUSEPORT. This may help the proxy scale better on multicore systems +# with a lot of incoming connections, and provide better CPU utilization than the existing +# strategy of having a single thread accepting connections and distributing them to worker +# threads. Disabled by default. Requires Linux or macOS. +enable-reuse-port = false + +# How fast (in milliseconds) are clients allowed to send commands after the last command +# By default this is 50ms (20 commands per second) +command-rate-limit = 50 + +# Should we forward commands to the backend upon being rate limited? +# This will forward the command to the server instead of processing it on the proxy. +# Since most server implementations have a rate limit, this will prevent the player +# from being able to send excessive commands to the server. +forward-commands-if-rate-limited = true + +# How many commands are allowed to be sent after the rate limit is hit before the player is kicked? +# Setting this to 0 or lower will disable this feature. +kick-after-rate-limited-commands = 0 + +# How fast (in milliseconds) are clients allowed to send tab completions after the last tab completion +tab-complete-rate-limit = 10 + +# How many tab completions are allowed to be sent after the rate limit is hit before the player is kicked? +# Setting this to 0 or lower will disable this feature. +kick-after-rate-limited-tab-completes = 0 + +[query] +# Whether to enable responding to GameSpy 4 query responses or not. +enabled = true + +# If query is enabled, on what port should the query protocol listen on? +port = 25578 + +# This is the map name that is reported to the query services. +map = "Velocity" + +# Whether plugins should be shown in query response by default or not +show-plugins = false diff --git a/test/HeadlessMC/templates/velocity/velocity.toml b/test/HeadlessMC/templates/velocity/modern/velocity.toml similarity index 99% rename from test/HeadlessMC/templates/velocity/velocity.toml rename to test/HeadlessMC/templates/velocity/modern/velocity.toml index f5b3cb4e..f80e95cd 100644 --- a/test/HeadlessMC/templates/velocity/velocity.toml +++ b/test/HeadlessMC/templates/velocity/modern/velocity.toml @@ -6,7 +6,7 @@ bind = "0.0.0.0:25577" # What should be the MOTD? This gets displayed when the player adds your server to # their server list. Only MiniMessage format is accepted. -motd = "<#09add3>A Velocity Server" +motd = "<#09add3>A Modern Velocity Server" # What should we display for the maximum number of players? (Velocity does not support a cap # on the number of players online.) @@ -64,7 +64,7 @@ kick-existing-players = false # description and mod list. # - "all": Uses the backend server's response as the proxy response. The Velocity # configuration is used if no servers could be contacted. -ping-passthrough = "ALL" +ping-passthrough = "MODS" # If enabled (default is false), then a sample of the online players on the proxy will be visible # when hovering over the player count in the server list. diff --git a/test/build.gradle.kts b/test/build.gradle.kts index 4456b8f5..3f37a3d6 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -7,8 +7,10 @@ import java.util.Locale // Create new ./test/HeadlessMC/velocity folder // Download into new velocity folder: https://papermc.io/downloads/velocity // Rename to velocity-proxy.jar -// Create new ./test/HeadlessMC/velocity/plugins folder -// Download into velocity plugins folder: https://modrinth.com/plugin/ambassador + +// Create new ./test/HeadlessMC/velocity/modern/plugins folder +// Download into velocity/modern/plugins folder: https://modrinth.com/plugin/ambassador +// Repeat for "legacy" and "bungeeguard" if you plan to test them // If you're testing modern forwarding with 1.7.2-1.12.2 you'll need the Velocity fork which adds that functionality // Build from: https://github.com/p0t4t0sandwich/Velocity/tree/feat/modern-forwarding-legacy @@ -26,9 +28,22 @@ val versions: Map> = mapOf( "neoforge" to listOf( "1.20.2", "1.20.4", "1.21.1", "1.21.5", "26.1.2" + ), + "vanilla" to listOf( + "1.12.2", "1.13.2", + "1.14.4", "1.15.2", "1.16.5", + "1.17.1", "1.18.2", "1.19", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", + "1.21.1", "1.21.5", + "26.1.2" ) ) +val forwardingModes = listOf( + "legacy", + "bungeeguard", + "modern" +) + val headlessJar: ConfigurableFileCollection = files("HeadlessMC/headlessmc-launcher-2.9.0.jar") val headlessMain: String = "io.github.headlesshq.headlessmc.launcher.Main" var headlessJavaArgs = listOf( @@ -38,8 +53,6 @@ var headlessJavaArgs = listOf( //"-Dhmc.always.lwjgl.flag=true", "-Dhmc.always.pauls.flag=true", - "-Dhmc.gameargs=--server 127.0.0.1 --port 25577 --quickPlayMultiplayer 127.0.0.1:25577", - "-Dhmc.server.accept.eula=true", "--enable-native-access=ALL-UNNAMED" ) @@ -84,6 +97,7 @@ tasks.register("headlessmc") { // Generate server setup tasks for each platform and version versions.forEach { (platform, mcVersions) -> + if (platform == "vanilla") return@forEach mcVersions.forEach { mcVersion -> val taskName = "setup${taskSuffix(platform, mcVersion)}" tasks.register(taskName) { @@ -121,49 +135,67 @@ versions.forEach { (platform, mcVersions) -> // Generate server run tasks for each platform and version versions.forEach { (platform, mcVersions) -> - mcVersions.forEach { mcVersion -> - val taskName = "run${taskSuffix(platform, mcVersion)}Server" - tasks.register(taskName) { - val finalJar = rootProject.tasks.getByName("finalJar") - dependsOn(finalJar) - doFirst { - val serverDir = file("HeadlessMC/servers/$platform/$mcVersion") - if (!serverDir.exists()) { - throw GradleException("Server for $platform $mcVersion not found. Please run setup task first.") - } - val parents = serverDir.walk().maxDepth(3) - .filter { it.isDirectory && it.name == "libraries" } - .map { it.parentFile } - .toList() - - parents.forEach { parent -> - val mods = parent.resolve("mods").apply { mkdirs() } - // Remove any proxy-compatible-forge jars from mods folder - parent.resolve("mods").listFiles()?.forEach { if (it.name.startsWith("proxy-compatible-forge-")) it.delete() } - copy { from(files(finalJar.archiveFile)); into(mods) } + if (platform == "vanilla") return@forEach + forwardingModes.forEach { forwardingMode -> + val parsedMode = forwardingMode.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + mcVersions.forEach { mcVersion -> + val taskName = "run${taskSuffix(platform, mcVersion)}Server$parsedMode" + tasks.register(taskName) { + val finalJar = rootProject.tasks.getByName("finalJar") + dependsOn(finalJar) + doFirst { + val serverDir = file("HeadlessMC/servers/$platform/$mcVersion") + if (!serverDir.exists()) { + throw GradleException("Server for $platform $mcVersion not found. Please run setup task first.") + } + val parents = serverDir.walk().maxDepth(3) + .filter { it.isDirectory && it.name == "libraries" } + .map { it.parentFile } + .toList() + + parents.forEach { parent -> + val mods = parent.resolve("mods").apply { mkdirs() } + // Remove any proxy-compatible-forge jars from mods folder + parent.resolve("mods").listFiles() + ?.forEach { if (it.name.startsWith("proxy-compatible-forge-")) it.delete() } + copy { from(files(finalJar.archiveFile)); into(mods) } + } } + group = "run_server_$forwardingMode" + classpath += headlessJar + mainClass.set(headlessMain) + environment("PCF_FORWARDING_MODE", forwardingMode) + jvmArgs(headlessJavaArgs) + args("--command server launch pcf-$platform-$mcVersion".split(" ")) + standardInput = System.`in` } - group = "run_server" - classpath += headlessJar - mainClass.set(headlessMain) - jvmArgs(headlessJavaArgs) - args("--command server launch pcf-$platform-$mcVersion".split(" ")) - standardInput = System.`in` } } } // Generate client run tasks for each platform and version versions.forEach { (platform, mcVersions) -> - mcVersions.forEach { mcVersion -> - val taskName = "run${taskSuffix(platform, mcVersion)}Client" - tasks.register(taskName) { - group = "run_client" - classpath += headlessJar - mainClass.set(headlessMain) - jvmArgs(headlessJavaArgs) - args("--command launch $platform:$mcVersion".split(" ")) - standardInput = System.`in` + forwardingModes.forEach { forwardingMode -> + val parsedMode = forwardingMode.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + mcVersions.forEach { mcVersion -> + val taskName = "run${taskSuffix(platform, mcVersion)}Client$parsedMode" + tasks.register(taskName) { + group = "run_client_$forwardingMode" + classpath += headlessJar + mainClass.set(headlessMain) + jvmArgs(headlessJavaArgs) + when (forwardingMode) { + "legacy" -> jvmArgs("-Dhmc.gameargs=--server 127.0.0.1 --port 25578 --quickPlayMultiplayer 127.0.0.1:25578") + "bungeeguard" -> jvmArgs("-Dhmc.gameargs=--server 127.0.0.1 --port 25579 --quickPlayMultiplayer 127.0.0.1:25579") + "modern" -> jvmArgs("-Dhmc.gameargs=--server 127.0.0.1 --port 25577 --quickPlayMultiplayer 127.0.0.1:25577") + } + if (platform == "vanilla") { + args("--command launch $mcVersion".split(" ")) + } else { + args("--command launch $platform:$mcVersion".split(" ")) + } + standardInput = System.`in` + } } } } @@ -179,18 +211,27 @@ val velocityJavaArgs = listOf( ) // Set up Velocity -tasks.register("setupVelocity") { - group = "setup_proxy" - from(file("HeadlessMC/templates/velocity")) - into(file("HeadlessMC/velocity")) +forwardingModes.forEach { forwardingMode -> + val parsedMode = forwardingMode.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + tasks.register("setupVelocity$parsedMode") { + group = "setup_proxy" + from(files( + "HeadlessMC/templates/velocity/common", + "HeadlessMC/templates/velocity/$forwardingMode" + )) + into(file("HeadlessMC/velocity/$forwardingMode")) + } } // Run Velocity -tasks.register("runVelocity") { - group = "run_proxy" - workingDir(file("HeadlessMC/velocity")) - classpath += velocityJar - mainClass.set(velocityMain) - jvmArgs(velocityJavaArgs) - standardInput = System.`in` +forwardingModes.forEach { forwardingMode -> + val parsedMode = forwardingMode.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + tasks.register("runVelocity$parsedMode") { + group = "run_proxy" + workingDir(file("HeadlessMC/velocity/$forwardingMode")) + classpath += velocityJar + mainClass.set(velocityMain) + jvmArgs(velocityJavaArgs) + standardInput = System.`in` + } } diff --git a/test/gradle.properties b/test/gradle.properties index 6ecdb355..926ce8bc 100644 --- a/test/gradle.properties +++ b/test/gradle.properties @@ -1 +1 @@ -run_offline=true +run_offline=false