diff --git a/build.gradle b/build.gradle index 819bede..279d346 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,9 @@ dependencies { include "net.lenni0451.classtransform:additionalclassprovider:1.12.1" include "net.lenni0451:Reflect:1.3.0" include "net.lenni0451:LambdaEvents:2.3.2" + include("net.lenni0451:MCPing:1.3.0") { + exclude group: "com.google.code.gson", module: "gson" + } include("net.raphimc.netminecraft:all:2.3.7-SNAPSHOT") { exclude group: "com.google.code.gson", module: "gson" } diff --git a/src/main/java/net/raphimc/viaproxy/ViaProxy.java b/src/main/java/net/raphimc/viaproxy/ViaProxy.java index 7e26a1e..36cf577 100644 --- a/src/main/java/net/raphimc/viaproxy/ViaProxy.java +++ b/src/main/java/net/raphimc/viaproxy/ViaProxy.java @@ -38,6 +38,7 @@ import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.netty.connection.NetServer; import net.raphimc.viaproxy.cli.ConsoleHandler; import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.injection.VersionEnumExtension; import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.events.Client2ProxyHandlerCreationEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; @@ -132,7 +133,8 @@ public class ViaProxy { ConsoleHandler.hookConsole(); ClassLoaderPriorityUtil.loadOverridingJars(); - loadNetty(); + ViaProxy.loadNetty(); + VersionEnumExtension.init(); SAVE_MANAGER = new SaveManager(); PLUGIN_MANAGER = new PluginManager(); diff --git a/src/main/java/net/raphimc/viaproxy/injection/VersionEnumExtension.java b/src/main/java/net/raphimc/viaproxy/injection/VersionEnumExtension.java new file mode 100644 index 0000000..2022ca9 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/VersionEnumExtension.java @@ -0,0 +1,38 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.injection; + +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import net.lenni0451.reflect.Enums; +import net.raphimc.vialoader.util.VersionEnum; + +public class VersionEnumExtension { + + private static final ProtocolVersion autoDetect = ProtocolVersion.register(-2, "Auto Detect (1.7+ servers)"); + public static final VersionEnum AUTO_DETECT = Enums.newInstance(VersionEnum.class, "AUTO_DETECT", VersionEnum.UNKNOWN.ordinal(), new Class[]{ProtocolVersion.class}, new Object[]{autoDetect}); + + static { + Enums.addEnumInstance(VersionEnum.class, AUTO_DETECT); + VersionEnum.SORTED_VERSIONS.add(AUTO_DETECT); + } + + public static void init() { + // calls static initializer + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java index da0433a..711ada3 100644 --- a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java @@ -21,12 +21,16 @@ import com.google.common.collect.ImmutableSet; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.protocol.version.VersionRange; import com.viaversion.viaversion.util.Pair; +import net.raphimc.viaproxy.injection.VersionEnumExtension; +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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.HashMap; import java.util.Map; @@ -35,6 +39,10 @@ import java.util.Set; @Mixin(value = ProtocolVersion.class, remap = false) public abstract class MixinProtocolVersion { + @Shadow + @Final + private int version; + @Unique private static Set skips; @@ -89,4 +97,11 @@ public abstract class MixinProtocolVersion { return ProtocolVersion.register(version, name, versionRange); } + @Inject(method = "isKnown", at = @At("HEAD"), cancellable = true) + private void markAutoDetectAsUnknown(CallbackInfoReturnable cir) { + if (VersionEnumExtension.AUTO_DETECT != null && this.version == VersionEnumExtension.AUTO_DETECT.getVersion()) { + cir.setReturnValue(false); + } + } + } diff --git a/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java index fb3026d..c1cf530 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java @@ -30,6 +30,7 @@ import net.raphimc.netminecraft.util.ServerAddress; import net.raphimc.vialoader.util.VersionEnum; import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.injection.VersionEnumExtension; import net.raphimc.viaproxy.plugins.events.ConnectEvent; import net.raphimc.viaproxy.plugins.events.PreConnectEvent; import net.raphimc.viaproxy.plugins.events.Proxy2ServerHandlerCreationEvent; @@ -44,12 +45,14 @@ import net.raphimc.viaproxy.proxy.session.ProxyConnection; import net.raphimc.viaproxy.proxy.session.UserOptions; import net.raphimc.viaproxy.proxy.util.*; import net.raphimc.viaproxy.util.ArrayHelper; +import net.raphimc.viaproxy.util.ProtocolVersionDetector; import net.raphimc.viaproxy.util.logging.Logger; import java.net.ConnectException; import java.net.InetSocketAddress; import java.nio.channels.UnresolvedAddressException; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -129,13 +132,8 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { if (arrayHelper.isIndexValid(3)) { classicMpPass = arrayHelper.getString(3); } - for (VersionEnum v : VersionEnum.getAllVersions()) { - if (v.getName().equalsIgnoreCase(versionString)) { - serverVersion = v; - break; - } - } - if (serverVersion == null) throw CloseAndReturn.INSTANCE; + serverVersion = VersionEnum.fromProtocolName(versionString); + if (serverVersion == VersionEnum.UNKNOWN) throw CloseAndReturn.INSTANCE; } else if (Options.SRV_MODE) { try { if (handshakeParts[0].toLowerCase().contains(".viaproxy.")) { @@ -150,13 +148,11 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { connectIP = arrayHelper.getAsString(0, arrayHelper.getLength() - 3, "_"); connectPort = arrayHelper.getInteger(arrayHelper.getLength() - 2); final String versionString = arrayHelper.get(arrayHelper.getLength() - 1); - for (VersionEnum v : VersionEnum.getAllVersions()) { - if (v.getName().replace(" ", "-").equalsIgnoreCase(versionString)) { - serverVersion = v; - break; - } + serverVersion = VersionEnum.fromProtocolName(versionString); + if (serverVersion == VersionEnum.UNKNOWN) { + serverVersion = VersionEnum.fromProtocolName(versionString.replace("-", " ")); } - if (serverVersion == null) throw CloseAndReturn.INSTANCE; + if (serverVersion == VersionEnum.UNKNOWN) throw CloseAndReturn.INSTANCE; } catch (CloseAndReturn e) { this.proxyConnection.kickClient("§cWrong SRV syntax! §6Please use:\n§7ip_port_version.viaproxy.hostname"); } @@ -184,6 +180,27 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { this.proxyConnection.kickClient(preConnectEvent.getCancelMessage()); } + final UserOptions userOptions = new UserOptions(classicMpPass, Options.MC_ACCOUNT); + ChannelUtil.disableAutoRead(this.proxyConnection.getC2P()); + + if (packet.intendedState == ConnectionState.LOGIN && serverVersion.equals(VersionEnumExtension.AUTO_DETECT)) { + CompletableFuture.runAsync(() -> { + final VersionEnum detectedVersion = ProtocolVersionDetector.get(serverAddress, clientVersion); + this.connect(serverAddress, detectedVersion, clientVersion, packet.intendedState, userOptions, handshakeParts); + }).exceptionally(t -> { + if (t instanceof ConnectException || t instanceof UnresolvedAddressException) { + this.proxyConnection.kickClient("§cCould not connect to the backend server!"); + } else { + this.proxyConnection.kickClient("§cAutomatic protocol detection failed!\n§c" + t.getMessage()); + } + return null; + }); + } else { + this.connect(serverAddress, serverVersion, clientVersion, packet.intendedState, userOptions, handshakeParts); + } + } + + private void connect(final ServerAddress serverAddress, final VersionEnum serverVersion, final VersionEnum clientVersion, final ConnectionState intendedState, final UserOptions userOptions, final String[] handshakeParts) { final Supplier handlerSupplier = () -> ViaProxy.EVENT_MANAGER.call(new Proxy2ServerHandlerCreationEvent(new Proxy2ServerHandler(), false)).getHandler(); if (serverVersion.equals(VersionEnum.bedrockLatest)) { this.proxyConnection = new BedrockProxyConnection(handlerSupplier, Proxy2ServerChannelInitializer::new, this.proxyConnection.getC2P()); @@ -192,8 +209,8 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { } this.proxyConnection.getC2P().attr(ProxyConnection.PROXY_CONNECTION_ATTRIBUTE_KEY).set(this.proxyConnection); this.proxyConnection.setClientVersion(clientVersion); - this.proxyConnection.setC2pConnectionState(packet.intendedState); - this.proxyConnection.setUserOptions(new UserOptions(classicMpPass, Options.MC_ACCOUNT)); + this.proxyConnection.setC2pConnectionState(intendedState); + this.proxyConnection.setUserOptions(userOptions); this.proxyConnection.getPacketHandlers().add(new StatusPacketHandler(this.proxyConnection)); this.proxyConnection.getPacketHandlers().add(new CustomPayloadPacketHandler(this.proxyConnection)); this.proxyConnection.getPacketHandlers().add(new CompressionPacketHandler(this.proxyConnection)); @@ -207,7 +224,6 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { this.proxyConnection.getPacketHandlers().add(new ResourcePackPacketHandler(this.proxyConnection)); this.proxyConnection.getPacketHandlers().add(new UnexpectedPacketHandler(this.proxyConnection)); - ChannelUtil.disableAutoRead(this.proxyConnection.getC2P()); Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "[" + clientVersion.getName() + " <-> " + serverVersion.getName() + "] Connecting to " + serverAddress.getAddress() + ":" + serverAddress.getPort()); ViaProxy.EVENT_MANAGER.call(new ConnectEvent(this.proxyConnection)); @@ -219,10 +235,10 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { } handshakeParts[0] = serverAddress.getAddress(); - final C2SHandshakePacket newHandshakePacket = new C2SHandshakePacket(clientVersion.getOriginalVersion(), String.join("\0", handshakeParts), serverAddress.getPort(), packet.intendedState); + final C2SHandshakePacket newHandshakePacket = new C2SHandshakePacket(clientVersion.getOriginalVersion(), String.join("\0", handshakeParts), serverAddress.getPort(), intendedState); this.proxyConnection.getChannel().writeAndFlush(newHandshakePacket).addListeners(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, (ChannelFutureListener) f2 -> { if (f2.isSuccess()) { - this.proxyConnection.setP2sConnectionState(packet.intendedState); + this.proxyConnection.setP2sConnectionState(intendedState); } }); @@ -232,7 +248,7 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { }, (ThrowingChannelFutureListener) f -> { if (!f.isSuccess()) { if (f.cause() instanceof ConnectException || f.cause() instanceof UnresolvedAddressException) { - this.proxyConnection.kickClient("§cCould not connect to the backend server!\n§cTry again in a few seconds."); + this.proxyConnection.kickClient("§cCould not connect to the backend server!"); } else { Logger.LOGGER.error("Error while connecting to the backend server", f.cause()); this.proxyConnection.kickClient("§cAn error occurred while connecting to the backend server: " + f.cause().getMessage() + "\n§cCheck the console for more information."); diff --git a/src/main/java/net/raphimc/viaproxy/util/ProtocolVersionDetector.java b/src/main/java/net/raphimc/viaproxy/util/ProtocolVersionDetector.java new file mode 100644 index 0000000..ff111b1 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/util/ProtocolVersionDetector.java @@ -0,0 +1,56 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.util; + +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import net.lenni0451.mcping.MCPing; +import net.lenni0451.mcping.responses.MCPingResponse; +import net.raphimc.netminecraft.util.ServerAddress; +import net.raphimc.vialoader.util.VersionEnum; + +public class ProtocolVersionDetector { + + private static final int TIMEOUT = 1000; + + public static VersionEnum get(final ServerAddress serverAddress, final VersionEnum clientVersion) { + MCPingResponse response = MCPing + .pingModern(clientVersion.getOriginalVersion()) + .address(serverAddress.getAddress(), serverAddress.getPort()) + .noResolve() + .timeout(TIMEOUT, TIMEOUT) + .getSync(); + + if (response.version.protocol == clientVersion.getOriginalVersion()) { // If the server is on the same version as the client, we can just connect + return clientVersion; + } else { // Else ping again with protocol id -1 to get the protocol id of the server + response = MCPing + .pingModern(-1) + .address(serverAddress.getAddress(), serverAddress.getPort()) + .noResolve() + .timeout(TIMEOUT, TIMEOUT) + .getSync(); + + if (ProtocolVersion.isRegistered(response.version.protocol)) { // If the protocol is registered, we can use it + return VersionEnum.fromProtocolId(response.version.protocol); + } else { + throw new RuntimeException("Unsupported protocol version: " + response.version.protocol); + } + } + } + +}