Added server version auto detect support

This commit is contained in:
RaphiMC 2023-11-30 19:01:49 +01:00
parent de82e4c598
commit 1706b8c718
No known key found for this signature in database
GPG key ID: 0F6BB0657A03AC94
6 changed files with 150 additions and 20 deletions

View file

@ -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"
}

View file

@ -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();

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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<String> 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<Boolean> cir) {
if (VersionEnumExtension.AUTO_DETECT != null && this.version == VersionEnumExtension.AUTO_DETECT.getVersion()) {
cir.setReturnValue(false);
}
}
}

View file

@ -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<IPacket> {
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<IPacket> {
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<IPacket> {
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<ChannelHandler> 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<IPacket> {
}
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<IPacket> {
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<IPacket> {
}
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<IPacket> {
}, (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.");

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}
}