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);
+ }
+ }
+ }
+
+}