diff --git a/README.md b/README.md index 1fce7b5..646ea5e 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ For a full user guide go to the [Usage for Players](#usage-for-players-gui) sect - Classic (c0.0.15 - c0.30 including [CPE](https://wiki.vg/Classic_Protocol_Extension)) - Alpha (a1.0.15 - a1.2.6) - Beta (b1.0 - b1.8.1) -- Release (1.0.0 - 1.20) +- Release (1.0.0 - 1.20.2) - April Fools (3D Shareware, 20w14infinite) - Combat Snapshots (Combat Test 8c) - Bedrock Edition 1.20.0 (In development) ## Supported Client versions -- Release (1.7.2 - 1.20) +- Release (1.7.2 - 1.20.2) - Bedrock Edition (Needs the [Geyser plugin](https://github.com/RaphiMC/ViaProxyGeyserPlugin)) - Classic, Alpha, Beta, Release 1.0 - 1.6.4 (Only passthrough) diff --git a/build.gradle b/build.gradle index 7b6c6a9..82d3237 100644 --- a/build.gradle +++ b/build.gradle @@ -76,21 +76,21 @@ repositories { dependencies { compileOnly sourceSets.java17compat.output - include "com.viaversion:viaversion:4.7.1-SNAPSHOT" - include("com.viaversion:viabackwards-common:4.7.1-SNAPSHOT") { + include "com.viaversion:viaversion:4.8.0-23w32a-SNAPSHOT" + include("com.viaversion:viabackwards-common:4.8.0-23w32a-SNAPSHOT") { exclude group: "com.viaversion", module: "viaversion" exclude group: "io.netty", module: "netty-all" exclude group: "com.google.guava", module: "guava" } - include "com.viaversion:viarewind-core:2.0.4-SNAPSHOT" - include "net.raphimc:ViaLegacy:2.2.18-SNAPSHOT" - include "net.raphimc:ViaAprilFools:2.0.7" + include "com.viaversion:viarewind-core:3.0.0-SNAPSHOT" + include "net.raphimc:ViaLegacy:2.2.19-SNAPSHOT" + include "net.raphimc:ViaAprilFools:2.0.9-SNAPSHOT" include("net.raphimc:ViaBedrock:0.0.2-SNAPSHOT") { exclude group: "io.netty", module: "netty-codec-http" exclude group: "io.jsonwebtoken", module: "jjwt-impl" exclude group: "io.jsonwebtoken", module: "jjwt-gson" } - include("net.raphimc:ViaLoader:2.2.7") { + include("net.raphimc:ViaLoader:2.2.9-SNAPSHOT") { exclude group: "org.slf4j", module: "slf4j-api" } @@ -108,7 +108,7 @@ dependencies { include "net.lenni0451.classtransform:additionalclassprovider:1.10.1" include "net.lenni0451:Reflect:1.2.2" include "net.lenni0451:LambdaEvents:2.2.0" - include "net.raphimc.netminecraft:all:2.3.4" + include "net.raphimc.netminecraft:all:2.3.6-SNAPSHOT" include("net.raphimc:MinecraftAuth:2.1.5-SNAPSHOT") { exclude group: "com.google.code.gson", module: "gson" exclude group: "org.slf4j", module: "slf4j-api" 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 4243319..df031d4 100644 --- a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java @@ -51,6 +51,7 @@ public abstract class MixinProtocolVersion { remaps.put("1.18/1.18.1", new Pair<>("1.18-1.18.1", null)); remaps.put("1.19.1/2", new Pair<>("1.19.1-1.19.2", null)); remaps.put("1.20/1.20.1", new Pair<>("1.20-1.20.1", null)); + remaps.put("1.20.2", new Pair<>("23w32a", null)); } @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;register(ILjava/lang/String;)Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;")) diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/ConnectEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/ConnectEvent.java new file mode 100644 index 0000000..f6f392d --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/ConnectEvent.java @@ -0,0 +1,35 @@ +/* + * 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.plugins.events; + +import net.raphimc.viaproxy.plugins.events.types.EventCancellable; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +public class ConnectEvent extends EventCancellable { + + private final ProxyConnection proxyConnection; + + public ConnectEvent(final ProxyConnection proxyConnection) { + this.proxyConnection = proxyConnection; + } + + public ProxyConnection getProxyConnection() { + return this.proxyConnection; + } + +} 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 26c87ca..ae7dfd1 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java @@ -17,9 +17,8 @@ */ package net.raphimc.viaproxy.proxy.client2proxy; -import com.mojang.authlib.GameProfile; +import com.google.common.collect.Lists; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; -import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -27,28 +26,19 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.haproxy.*; import net.raphimc.netminecraft.constants.ConnectionState; -import net.raphimc.netminecraft.constants.MCPackets; -import net.raphimc.netminecraft.constants.MCPipeline; -import net.raphimc.netminecraft.netty.crypto.AESEncryption; -import net.raphimc.netminecraft.netty.crypto.CryptUtil; import net.raphimc.netminecraft.packet.IPacket; -import net.raphimc.netminecraft.packet.PacketTypes; -import net.raphimc.netminecraft.packet.UnknownPacket; import net.raphimc.netminecraft.packet.impl.handshake.C2SHandshakePacket; -import net.raphimc.netminecraft.packet.impl.login.*; 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.plugins.PluginManager; +import net.raphimc.viaproxy.plugins.events.ConnectEvent; import net.raphimc.viaproxy.plugins.events.PreConnectEvent; import net.raphimc.viaproxy.plugins.events.Proxy2ServerHandlerCreationEvent; import net.raphimc.viaproxy.plugins.events.ResolveSrvEvent; import net.raphimc.viaproxy.protocolhack.viaproxy.ViaBedrockTransferHolder; -import net.raphimc.viaproxy.proxy.LoginState; -import net.raphimc.viaproxy.proxy.external_interface.AuthLibServices; -import net.raphimc.viaproxy.proxy.external_interface.ExternalInterface; -import net.raphimc.viaproxy.proxy.external_interface.OpenAuthModConstants; +import net.raphimc.viaproxy.proxy.packethandler.*; import net.raphimc.viaproxy.proxy.proxy2server.Proxy2ServerChannelInitializer; import net.raphimc.viaproxy.proxy.proxy2server.Proxy2ServerHandler; import net.raphimc.viaproxy.proxy.session.BedrockProxyConnection; @@ -59,41 +49,24 @@ import net.raphimc.viaproxy.proxy.util.ExceptionUtil; import net.raphimc.viaproxy.util.ArrayHelper; import net.raphimc.viaproxy.util.logging.Logger; -import javax.crypto.SecretKey; -import java.math.BigInteger; import java.net.ConnectException; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.nio.channels.UnresolvedAddressException; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.time.Instant; -import java.util.Arrays; import java.util.Collections; -import java.util.Random; +import java.util.List; import java.util.function.Supplier; import java.util.regex.Pattern; public class Client2ProxyHandler extends SimpleChannelInboundHandler { - private static final KeyPair KEY_PAIR = CryptUtil.generateKeyPair(); - private static final Random RANDOM = new Random(); - private ProxyConnection proxyConnection; - private LoginState loginState = LoginState.FIRST_PACKET; - - private final byte[] verifyToken = new byte[4]; - private int customPayloadPacketId = -1; - private int chatSessionUpdatePacketId = -1; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); - RANDOM.nextBytes(this.verifyToken); this.proxyConnection = new DummyProxyConnection(ctx.channel()); - ViaProxy.c2pChannels.add(ctx.channel()); } @@ -112,30 +85,19 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { protected void channelRead0(ChannelHandlerContext ctx, IPacket packet) throws Exception { if (this.proxyConnection.isClosed()) return; - switch (this.proxyConnection.getConnectionState()) { - case HANDSHAKING: - if (packet instanceof C2SHandshakePacket) this.handleHandshake((C2SHandshakePacket) packet); - else throw new IllegalStateException("Unexpected packet in HANDSHAKING state"); - - return; - case LOGIN: - if (packet instanceof C2SLoginHelloPacket1_7) this.handleLoginHello((C2SLoginHelloPacket1_7) packet); - else if (packet instanceof C2SLoginKeyPacket1_7) this.handleLoginKey((C2SLoginKeyPacket1_7) packet); - else if (packet instanceof C2SLoginCustomPayloadPacket) this.handleLoginCustomPayload((C2SLoginCustomPayloadPacket) packet); - else throw new IllegalStateException("Unexpected packet in LOGIN state"); - - return; - case PLAY: - final UnknownPacket unknownPacket = (UnknownPacket) packet; - if (unknownPacket.packetId == this.customPayloadPacketId) { - if (this.handlePlayCustomPayload(Unpooled.wrappedBuffer(unknownPacket.data))) return; - } else if (unknownPacket.packetId == this.chatSessionUpdatePacketId && this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).get() == null) { - return; - } - break; + if (this.proxyConnection.getConnectionState() == ConnectionState.HANDSHAKING) { + if (packet instanceof C2SHandshakePacket) this.handleHandshake((C2SHandshakePacket) packet); + else throw new IllegalStateException("Unexpected packet in HANDSHAKING state"); + return; } - this.proxyConnection.getChannel().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + final List listeners = Lists.newArrayList(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + for (PacketHandler packetHandler : this.proxyConnection.getPacketHandlers()) { + if (!packetHandler.handleC2P(packet, listeners)) { + return; + } + } + this.proxyConnection.getChannel().writeAndFlush(packet).addListeners(listeners.toArray(new ChannelFutureListener[0])); } @Override @@ -157,9 +119,6 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { this.proxyConnection.kickClient("§cYour client version is not supported by ViaProxy!"); } - this.customPayloadPacketId = MCPackets.C2S_PLUGIN_MESSAGE.getId(clientVersion.getVersion()); - this.chatSessionUpdatePacketId = MCPackets.C2S_CHAT_SESSION_UPDATE.getId(clientVersion.getVersion()); - String[] handshakeParts = new String[]{packet.address}; if (Options.PLAYER_INFO_FORWARDING) { handshakeParts = new String[3]; @@ -256,9 +215,16 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { this.proxyConnection.setClientVersion(clientVersion); this.proxyConnection.setConnectionState(packet.intendedState); this.proxyConnection.setClassicMpPass(classicMpPass); + this.proxyConnection.getPacketHandlers().add(new StatusPacketHandler(this.proxyConnection)); + this.proxyConnection.getPacketHandlers().add(new CustomPayloadPacketHandler(this.proxyConnection)); + this.proxyConnection.getPacketHandlers().add(new LoginPacketHandler(this.proxyConnection)); + this.proxyConnection.getPacketHandlers().add(new ConfigurationPacketHandler(this.proxyConnection)); + this.proxyConnection.getPacketHandlers().add(new ResourcePackPacketHandler(this.proxyConnection)); + this.proxyConnection.getPacketHandlers().add(new UnexpectedPacketHandler(this.proxyConnection)); Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "[" + clientVersion.getName() + " <-> " + serverVersion.getName() + "] Connecting to " + serverAddress.getAddress() + ":" + serverAddress.getPort()); try { + PluginManager.EVENT_MANAGER.call(new ConnectEvent(this.proxyConnection)); this.proxyConnection.connectToServer(serverAddress, serverVersion); } catch (Throwable e) { if (e instanceof ConnectException || e instanceof UnresolvedAddressException) { // Trust me, this is not always false @@ -284,91 +250,4 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { this.proxyConnection.setConnectionState(packet.intendedState); } - private void handleLoginHello(C2SLoginHelloPacket1_7 packet) { - if (this.loginState != LoginState.FIRST_PACKET) throw CloseAndReturn.INSTANCE; - this.loginState = LoginState.SENT_HELLO; - - if (packet instanceof C2SLoginHelloPacket1_19) { - final C2SLoginHelloPacket1_19 packet1_19 = (C2SLoginHelloPacket1_19) packet; - if (packet1_19.expiresAt != null && packet1_19.expiresAt.isBefore(Instant.now())) { - throw new IllegalStateException("Expired public key"); - } - } - - proxyConnection.setLoginHelloPacket(packet); - if (packet instanceof C2SLoginHelloPacket1_19_3) { - proxyConnection.setGameProfile(new GameProfile(((C2SLoginHelloPacket1_19_3) packet).uuid, packet.name)); - } else { - proxyConnection.setGameProfile(new GameProfile(null, packet.name)); - } - - if (Options.ONLINE_MODE) { - this.proxyConnection.getC2P().writeAndFlush(new S2CLoginKeyPacket1_8("", KEY_PAIR.getPublic().getEncoded(), this.verifyToken)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } else { - ExternalInterface.fillPlayerData(this.proxyConnection); - this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } - } - - private void handleLoginKey(final C2SLoginKeyPacket1_7 packet) throws GeneralSecurityException { - if (this.proxyConnection.getClientVersion().isOlderThanOrEqualTo(VersionEnum.r1_12_2) && new String(packet.encryptedNonce, StandardCharsets.UTF_8).equals(OpenAuthModConstants.DATA_CHANNEL)) { // 1.8-1.12.2 OpenAuthMod response handling - final ByteBuf byteBuf = Unpooled.wrappedBuffer(packet.encryptedSecretKey); - this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf); - return; - } - - if (this.loginState != LoginState.SENT_HELLO) throw CloseAndReturn.INSTANCE; - this.loginState = LoginState.SENT_KEY; - - if (packet.encryptedNonce != null) { - if (!Arrays.equals(this.verifyToken, CryptUtil.decryptData(KEY_PAIR.getPrivate(), packet.encryptedNonce))) { - Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token"); - this.proxyConnection.kickClient("§cInvalid verify token!"); - } - } else { - final C2SLoginKeyPacket1_19 keyPacket = (C2SLoginKeyPacket1_19) packet; - final C2SLoginHelloPacket1_19 helloPacket = (C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket(); - if (helloPacket.key == null || !CryptUtil.verifySignedNonce(helloPacket.key, this.verifyToken, keyPacket.salt, keyPacket.signature)) { - Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token"); - this.proxyConnection.kickClient("§cInvalid verify token!"); - } - } - - final SecretKey secretKey = CryptUtil.decryptSecretKey(KEY_PAIR.getPrivate(), packet.encryptedSecretKey); - this.proxyConnection.getC2P().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey)); - - final String userName = this.proxyConnection.getGameProfile().getName(); - - try { - final String serverHash = new BigInteger(CryptUtil.computeServerIdHash("", KEY_PAIR.getPublic(), secretKey)).toString(16); - final GameProfile mojangProfile = AuthLibServices.SESSION_SERVICE.hasJoinedServer(this.proxyConnection.getGameProfile(), serverHash, null); - if (mojangProfile == null) { - Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid session"); - this.proxyConnection.kickClient("§cInvalid session! Please restart minecraft (and the launcher) and try again."); - } else { - this.proxyConnection.setGameProfile(mojangProfile); - } - Logger.u_info("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Authenticated as " + this.proxyConnection.getGameProfile().getId().toString()); - } catch (Throwable e) { - throw new RuntimeException("Failed to make session request for user '" + userName + "'!", e); - } - - ExternalInterface.fillPlayerData(this.proxyConnection); - this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } - - private void handleLoginCustomPayload(final C2SLoginCustomPayloadPacket packet) { - if (packet.response == null || !this.proxyConnection.handleCustomPayload(packet.queryId, Unpooled.wrappedBuffer(packet.response))) { - this.proxyConnection.getChannel().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } - } - - private boolean handlePlayCustomPayload(final ByteBuf packet) { - final String channel = PacketTypes.readString(packet, Short.MAX_VALUE); // channel - if (channel.equals(OpenAuthModConstants.DATA_CHANNEL)) { - return this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(packet), packet); - } - return false; - } - } diff --git a/src/main/java/net/raphimc/viaproxy/proxy/external_interface/ExternalInterface.java b/src/main/java/net/raphimc/viaproxy/proxy/external_interface/ExternalInterface.java index 59eead2..8e9aee6 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/external_interface/ExternalInterface.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/external_interface/ExternalInterface.java @@ -26,6 +26,7 @@ import net.raphimc.mcauth.step.bedrock.StepMCChain; import net.raphimc.mcauth.step.java.StepPlayerCertificates; import net.raphimc.netminecraft.packet.PacketTypes; import net.raphimc.netminecraft.packet.impl.login.C2SLoginHelloPacket1_19_3; +import net.raphimc.netminecraft.packet.impl.login.C2SLoginHelloPacket1_20_2; import net.raphimc.netminecraft.packet.impl.login.C2SLoginKeyPacket1_19; import net.raphimc.viabedrock.protocol.storage.AuthChainData; import net.raphimc.vialoader.util.VersionEnum; @@ -78,7 +79,7 @@ public class ExternalInterface { if (proxyConnection.getClientVersion().equals(VersionEnum.r1_19)) { loginHelloKeySignature = playerCertificates.legacyPublicKeySignature(); } - proxyConnection.setLoginHelloPacket(new C2SLoginHelloPacket1_19_3(proxyConnection.getGameProfile().getName(), expiresAt, publicKey, loginHelloKeySignature, proxyConnection.getGameProfile().getId())); + proxyConnection.setLoginHelloPacket(new C2SLoginHelloPacket1_20_2(proxyConnection.getGameProfile().getName(), expiresAt, publicKey, loginHelloKeySignature, proxyConnection.getGameProfile().getId())); user.put(new ChatSession1_19_0(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, playerCertificates.legacyPublicKeySignature()))); user.put(new ChatSession1_19_1(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, keySignature))); diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ConfigurationPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ConfigurationPacketHandler.java new file mode 100644 index 0000000..a685639 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ConfigurationPacketHandler.java @@ -0,0 +1,94 @@ +/* + * 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.proxy.packethandler; + +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.ConnectionState; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.UnknownPacket; +import net.raphimc.netminecraft.packet.impl.configuration.C2SConfigFinishConfiguration1_20_2; +import net.raphimc.netminecraft.packet.impl.configuration.S2CConfigFinishConfiguration1_20_2; +import net.raphimc.netminecraft.packet.impl.login.C2SLoginStartConfiguration1_20_2; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; +import net.raphimc.viaproxy.util.logging.Logger; + +import java.util.List; + +public class ConfigurationPacketHandler extends PacketHandler { + + private final int configurationAcknowledgedId; + private final int startConfigurationId; + + public ConfigurationPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + + this.configurationAcknowledgedId = MCPackets.C2S_CONFIGURATION_ACKNOWLEDGED.getId(proxyConnection.getClientVersion().getVersion()); + this.startConfigurationId = MCPackets.S2C_START_CONFIGURATION.getId(proxyConnection.getClientVersion().getVersion()); + } + + @Override + public boolean handleC2P(IPacket packet, List listeners) { + if (packet instanceof UnknownPacket) { + final UnknownPacket unknownPacket = (UnknownPacket) packet; + if (unknownPacket.packetId == this.configurationAcknowledgedId) { + listeners.add(f -> { + if (f.isSuccess()) { + Logger.u_info("session", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Switching to CONFIGURATION state"); + this.proxyConnection.setConnectionState(ConnectionState.CONFIGURATION); + this.proxyConnection.getChannel().config().setAutoRead(true); + } + }); + } + } else if (packet instanceof C2SLoginStartConfiguration1_20_2) { + this.proxyConnection.getC2P().config().setAutoRead(false); + listeners.add(f -> { + if (f.isSuccess()) { + this.proxyConnection.setConnectionState(ConnectionState.CONFIGURATION); + this.proxyConnection.getChannel().config().setAutoRead(true); + this.proxyConnection.getC2P().config().setAutoRead(true); + } + }); + } else if (packet instanceof C2SConfigFinishConfiguration1_20_2) { + listeners.add(f -> { + if (f.isSuccess()) { + Logger.u_info("session", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Configuration finished! Switching to PLAY state"); + this.proxyConnection.setConnectionState(ConnectionState.PLAY); + this.proxyConnection.getChannel().config().setAutoRead(true); + } + }); + } + + return true; + } + + @Override + public boolean handleP2S(IPacket packet, List listeners) { + if (packet instanceof UnknownPacket) { + final UnknownPacket unknownPacket = (UnknownPacket) packet; + if (unknownPacket.packetId == this.startConfigurationId) { + this.proxyConnection.getChannel().config().setAutoRead(false); + } + } else if (packet instanceof S2CConfigFinishConfiguration1_20_2) { + this.proxyConnection.getChannel().config().setAutoRead(false); + } + + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/CustomPayloadPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/CustomPayloadPacketHandler.java new file mode 100644 index 0000000..0e1d517 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/CustomPayloadPacketHandler.java @@ -0,0 +1,75 @@ +/* + * 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.proxy.packethandler; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.PacketTypes; +import net.raphimc.netminecraft.packet.UnknownPacket; +import net.raphimc.netminecraft.packet.impl.login.C2SLoginCustomPayloadPacket; +import net.raphimc.netminecraft.packet.impl.login.C2SLoginKeyPacket1_7; +import net.raphimc.vialoader.util.VersionEnum; +import net.raphimc.viaproxy.proxy.external_interface.OpenAuthModConstants; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class CustomPayloadPacketHandler extends PacketHandler { + + private final int customPayloadId; + + public CustomPayloadPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + + this.customPayloadId = MCPackets.C2S_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion()); + } + + @Override + public boolean handleC2P(IPacket packet, List listeners) { + if (packet instanceof UnknownPacket) { + final UnknownPacket unknownPacket = (UnknownPacket) packet; + if (unknownPacket.packetId == this.customPayloadId) { + final ByteBuf data = Unpooled.wrappedBuffer(unknownPacket.data); + final String channel = PacketTypes.readString(data, Short.MAX_VALUE); // channel + if (channel.equals(OpenAuthModConstants.DATA_CHANNEL) && this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(data), data)) { + return false; + } + } + } else if (packet instanceof C2SLoginCustomPayloadPacket) { + final C2SLoginCustomPayloadPacket loginCustomPayload = (C2SLoginCustomPayloadPacket) packet; + if (loginCustomPayload.response != null && this.proxyConnection.handleCustomPayload(loginCustomPayload.queryId, Unpooled.wrappedBuffer(loginCustomPayload.response))) { + return false; + } + } else if (packet instanceof C2SLoginKeyPacket1_7) { + final C2SLoginKeyPacket1_7 loginKeyPacket = (C2SLoginKeyPacket1_7) packet; + + if (this.proxyConnection.getClientVersion().isOlderThanOrEqualTo(VersionEnum.r1_12_2) && new String(loginKeyPacket.encryptedNonce, StandardCharsets.UTF_8).equals(OpenAuthModConstants.DATA_CHANNEL)) { // 1.8-1.12.2 OpenAuthMod response handling + final ByteBuf byteBuf = Unpooled.wrappedBuffer(loginKeyPacket.encryptedSecretKey); + this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf); + return false; + } + } + + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java new file mode 100644 index 0000000..5147584 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java @@ -0,0 +1,219 @@ +/* + * 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.proxy.packethandler; + +import com.mojang.authlib.GameProfile; +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.ConnectionState; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.constants.MCPipeline; +import net.raphimc.netminecraft.netty.crypto.AESEncryption; +import net.raphimc.netminecraft.netty.crypto.CryptUtil; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.UnknownPacket; +import net.raphimc.netminecraft.packet.impl.login.*; +import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.storage.ProtocolMetadataStorage; +import net.raphimc.vialoader.util.VersionEnum; +import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.proxy.LoginState; +import net.raphimc.viaproxy.proxy.external_interface.AuthLibServices; +import net.raphimc.viaproxy.proxy.external_interface.ExternalInterface; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; +import net.raphimc.viaproxy.proxy.util.CloseAndReturn; +import net.raphimc.viaproxy.util.logging.Logger; + +import javax.crypto.SecretKey; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutionException; + +public class LoginPacketHandler extends PacketHandler { + + private static final KeyPair KEY_PAIR = CryptUtil.generateKeyPair(); + private static final Random RANDOM = new Random(); + + private final byte[] verifyToken = new byte[4]; + private LoginState loginState = LoginState.FIRST_PACKET; + + private final int chatSessionUpdateId; + + public LoginPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + + RANDOM.nextBytes(this.verifyToken); + this.chatSessionUpdateId = MCPackets.C2S_CHAT_SESSION_UPDATE.getId(proxyConnection.getClientVersion().getVersion()); + } + + @Override + public boolean handleC2P(IPacket packet, List listeners) throws GeneralSecurityException { + if (packet instanceof UnknownPacket) { + final UnknownPacket unknownPacket = (UnknownPacket) packet; + if (unknownPacket.packetId == this.chatSessionUpdateId && this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).get() == null) { + return false; + } + } else if (packet instanceof C2SLoginHelloPacket1_7) { + if (this.loginState != LoginState.FIRST_PACKET) throw CloseAndReturn.INSTANCE; + this.loginState = LoginState.SENT_HELLO; + final C2SLoginHelloPacket1_7 loginHelloPacket = (C2SLoginHelloPacket1_7) packet; + + if (packet instanceof C2SLoginHelloPacket1_19) { + final C2SLoginHelloPacket1_19 packet1_19 = (C2SLoginHelloPacket1_19) packet; + if (packet1_19.expiresAt != null && packet1_19.expiresAt.isBefore(Instant.now())) { + throw new IllegalStateException("Expired public key"); + } + } + + proxyConnection.setLoginHelloPacket(loginHelloPacket); + if (packet instanceof C2SLoginHelloPacket1_19_3) { + proxyConnection.setGameProfile(new GameProfile(((C2SLoginHelloPacket1_19_3) packet).uuid, loginHelloPacket.name)); + } else { + proxyConnection.setGameProfile(new GameProfile(null, loginHelloPacket.name)); + } + + if (Options.ONLINE_MODE) { + this.proxyConnection.getC2P().writeAndFlush(new S2CLoginKeyPacket1_8("", KEY_PAIR.getPublic().getEncoded(), this.verifyToken)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } else { + ExternalInterface.fillPlayerData(this.proxyConnection); + this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + + return false; + } else if (packet instanceof C2SLoginKeyPacket1_7) { + if (this.loginState != LoginState.SENT_HELLO) throw CloseAndReturn.INSTANCE; + this.loginState = LoginState.SENT_KEY; + final C2SLoginKeyPacket1_7 loginKeyPacket = (C2SLoginKeyPacket1_7) packet; + + if (loginKeyPacket.encryptedNonce != null) { + if (!Arrays.equals(this.verifyToken, CryptUtil.decryptData(KEY_PAIR.getPrivate(), loginKeyPacket.encryptedNonce))) { + Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token"); + this.proxyConnection.kickClient("§cInvalid verify token!"); + } + } else { + final C2SLoginKeyPacket1_19 keyPacket = (C2SLoginKeyPacket1_19) packet; + final C2SLoginHelloPacket1_19 helloPacket = (C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket(); + if (helloPacket.key == null || !CryptUtil.verifySignedNonce(helloPacket.key, this.verifyToken, keyPacket.salt, keyPacket.signature)) { + Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token"); + this.proxyConnection.kickClient("§cInvalid verify token!"); + } + } + + final SecretKey secretKey = CryptUtil.decryptSecretKey(KEY_PAIR.getPrivate(), loginKeyPacket.encryptedSecretKey); + this.proxyConnection.getC2P().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey)); + + final String userName = this.proxyConnection.getGameProfile().getName(); + + try { + final String serverHash = new BigInteger(CryptUtil.computeServerIdHash("", KEY_PAIR.getPublic(), secretKey)).toString(16); + final GameProfile mojangProfile = AuthLibServices.SESSION_SERVICE.hasJoinedServer(this.proxyConnection.getGameProfile(), serverHash, null); + if (mojangProfile == null) { + Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid session"); + this.proxyConnection.kickClient("§cInvalid session! Please restart minecraft (and the launcher) and try again."); + } else { + this.proxyConnection.setGameProfile(mojangProfile); + } + Logger.u_info("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Authenticated as " + this.proxyConnection.getGameProfile().getId().toString()); + } catch (Throwable e) { + throw new RuntimeException("Failed to make session request for user '" + userName + "'!", e); + } + + ExternalInterface.fillPlayerData(this.proxyConnection); + this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + + return false; + } + + return true; + } + + @Override + public boolean handleP2S(IPacket packet, List listeners) throws GeneralSecurityException, ExecutionException, InterruptedException { + if (packet instanceof S2CLoginKeyPacket1_7) { + final S2CLoginKeyPacket1_7 loginKeyPacket = (S2CLoginKeyPacket1_7) packet; + + final PublicKey publicKey = CryptUtil.decodeRsaPublicKey(loginKeyPacket.publicKey); + final SecretKey secretKey = CryptUtil.generateSecretKey(); + final String serverHash = new BigInteger(CryptUtil.computeServerIdHash(loginKeyPacket.serverId, publicKey, secretKey)).toString(16); + + boolean auth = true; + if (this.proxyConnection.getServerVersion().isOlderThanOrEqualTo(VersionEnum.r1_6_4)) { + auth = this.proxyConnection.getUserConnection().get(ProtocolMetadataStorage.class).authenticate; + } + if (auth) { + ExternalInterface.joinServer(serverHash, this.proxyConnection); + } + + final byte[] encryptedSecretKey = CryptUtil.encryptData(publicKey, secretKey.getEncoded()); + final byte[] encryptedNonce = CryptUtil.encryptData(publicKey, loginKeyPacket.nonce); + + final C2SLoginKeyPacket1_19_3 loginKey = new C2SLoginKeyPacket1_19_3(encryptedSecretKey, encryptedNonce); + if (this.proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_19) && this.proxyConnection.getLoginHelloPacket() instanceof C2SLoginHelloPacket1_19 && ((C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket()).key != null) { + ExternalInterface.signNonce(loginKeyPacket.nonce, loginKey, this.proxyConnection); + } + this.proxyConnection.getChannel().writeAndFlush(loginKey).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + + if (this.proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) { + this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey)); + } else { + this.proxyConnection.setKeyForPreNettyEncryption(secretKey); + } + + return false; + } else if (packet instanceof S2CLoginSuccessPacket1_7) { + final S2CLoginSuccessPacket1_7 loginSuccessPacket = (S2CLoginSuccessPacket1_7) packet; + + if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) { + if (Options.COMPRESSION_THRESHOLD > -1 && this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).get() == -1) { + this.proxyConnection.getChannel().config().setAutoRead(false); + this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListeners(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, (ChannelFutureListener) f -> { + if (f.isSuccess()) { + this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD); + this.proxyConnection.getChannel().config().setAutoRead(true); + } + }); + } + } + + final ConnectionState nextState = this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_20_2) ? ConnectionState.CONFIGURATION : ConnectionState.PLAY; + + this.proxyConnection.setGameProfile(new GameProfile(loginSuccessPacket.uuid, loginSuccessPacket.name)); + Logger.u_info("session", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Connected successfully! Switching to " + nextState + " state"); + + this.proxyConnection.getChannel().config().setAutoRead(false); + listeners.add(f -> { + if (f.isSuccess() && nextState != ConnectionState.CONFIGURATION) { + this.proxyConnection.setConnectionState(nextState); + this.proxyConnection.getChannel().config().setAutoRead(true); + } + }); + } else if (packet instanceof S2CLoginCompressionPacket) { + final S2CLoginCompressionPacket loginCompressionPacket = (S2CLoginCompressionPacket) packet; + + this.proxyConnection.getChannel().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(loginCompressionPacket.compressionThreshold); + return false; + } + + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/PacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/PacketHandler.java new file mode 100644 index 0000000..a14f56a --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/PacketHandler.java @@ -0,0 +1,42 @@ +/* + * 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.proxy.packethandler; + +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.util.List; + +public abstract class PacketHandler { + + protected final ProxyConnection proxyConnection; + + public PacketHandler(final ProxyConnection proxyConnection) { + this.proxyConnection = proxyConnection; + } + + public boolean handleC2P(final IPacket packet, final List listeners) throws Exception { + return true; + } + + public boolean handleP2S(final IPacket packet, final List listeners) throws Exception { + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ResourcePackPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ResourcePackPacketHandler.java new file mode 100644 index 0000000..0752745 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ResourcePackPacketHandler.java @@ -0,0 +1,96 @@ +/* + * 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.proxy.packethandler; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.libs.gson.JsonElement; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.PacketTypes; +import net.raphimc.netminecraft.packet.UnknownPacket; +import net.raphimc.vialoader.util.VersionEnum; +import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class ResourcePackPacketHandler extends PacketHandler { + + private final int joinGameId; + + public ResourcePackPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + + this.joinGameId = MCPackets.S2C_JOIN_GAME.getId(this.proxyConnection.getClientVersion().getVersion()); + } + + @Override + public boolean handleP2S(IPacket packet, List listeners) { + if (packet instanceof UnknownPacket) { + final UnknownPacket unknownPacket = (UnknownPacket) packet; + if (unknownPacket.packetId == this.joinGameId) { + listeners.add(f -> { + if (f.isSuccess()) { + this.sendResourcePack(); + } + }); + } + } + + return true; + } + + private void sendResourcePack() { + if (Options.RESOURCE_PACK_URL != null) { + this.proxyConnection.getChannel().eventLoop().schedule(() -> { + if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) { + final ByteBuf resourcePackPacket = Unpooled.buffer(); + PacketTypes.writeVarInt(resourcePackPacket, MCPackets.S2C_RESOURCE_PACK.getId(this.proxyConnection.getClientVersion().getVersion())); + PacketTypes.writeString(resourcePackPacket, Options.RESOURCE_PACK_URL); // url + PacketTypes.writeString(resourcePackPacket, ""); // hash + if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_17)) { + resourcePackPacket.writeBoolean(Via.getConfig().isForcedUse1_17ResourcePack()); // required + final JsonElement promptMessage = Via.getConfig().get1_17ResourcePackPrompt(); + if (promptMessage != null) { + resourcePackPacket.writeBoolean(true); // has message + PacketTypes.writeString(resourcePackPacket, promptMessage.toString()); // message + } else { + resourcePackPacket.writeBoolean(false); // has message + } + } + this.proxyConnection.getC2P().writeAndFlush(resourcePackPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } else if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) { + final byte[] data = Options.RESOURCE_PACK_URL.getBytes(StandardCharsets.UTF_8); + + final ByteBuf customPayloadPacket = Unpooled.buffer(); + PacketTypes.writeVarInt(customPayloadPacket, MCPackets.S2C_PLUGIN_MESSAGE.getId(this.proxyConnection.getClientVersion().getVersion())); + PacketTypes.writeString(customPayloadPacket, "MC|RPack"); // channel + customPayloadPacket.writeShort(data.length); // length + customPayloadPacket.writeBytes(data); // data + this.proxyConnection.getC2P().writeAndFlush(customPayloadPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + }, 250, TimeUnit.MILLISECONDS); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/StatusPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/StatusPacketHandler.java new file mode 100644 index 0000000..7052312 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/StatusPacketHandler.java @@ -0,0 +1,42 @@ +/* + * 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.proxy.packethandler; + +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.impl.status.S2CStatusPongPacket; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.util.List; + +public class StatusPacketHandler extends PacketHandler { + + public StatusPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + } + + @Override + public boolean handleP2S(IPacket packet, List listeners) { + if (packet instanceof S2CStatusPongPacket) { + listeners.add(ChannelFutureListener.CLOSE); + } + + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/UnexpectedPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/UnexpectedPacketHandler.java new file mode 100644 index 0000000..13f5124 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/UnexpectedPacketHandler.java @@ -0,0 +1,43 @@ +/* + * 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.proxy.packethandler; + +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.ConnectionState; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.util.List; + +public class UnexpectedPacketHandler extends PacketHandler { + + public UnexpectedPacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + } + + @Override + public boolean handleC2P(IPacket packet, List listeners) { + final ConnectionState connectionState = this.proxyConnection.getConnectionState(); + if (connectionState.equals(ConnectionState.HANDSHAKING)) { + throw new IllegalStateException("Unexpected packet in " + connectionState + " state"); + } + + return true; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java index aaf92a9..b5c3d15 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java @@ -17,53 +17,27 @@ */ package net.raphimc.viaproxy.proxy.proxy2server; -import com.mojang.authlib.GameProfile; -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.libs.gson.JsonElement; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import com.google.common.collect.Lists; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import net.raphimc.netminecraft.constants.ConnectionState; -import net.raphimc.netminecraft.constants.MCPackets; -import net.raphimc.netminecraft.constants.MCPipeline; -import net.raphimc.netminecraft.netty.crypto.AESEncryption; -import net.raphimc.netminecraft.netty.crypto.CryptUtil; import net.raphimc.netminecraft.packet.IPacket; -import net.raphimc.netminecraft.packet.PacketTypes; -import net.raphimc.netminecraft.packet.UnknownPacket; -import net.raphimc.netminecraft.packet.impl.login.*; -import net.raphimc.netminecraft.packet.impl.status.S2CPingResponsePacket; -import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.storage.ProtocolMetadataStorage; -import net.raphimc.vialoader.util.VersionEnum; -import net.raphimc.viaproxy.cli.options.Options; -import net.raphimc.viaproxy.proxy.external_interface.ExternalInterface; +import net.raphimc.viaproxy.proxy.packethandler.PacketHandler; import net.raphimc.viaproxy.proxy.session.ProxyConnection; import net.raphimc.viaproxy.proxy.util.ExceptionUtil; import net.raphimc.viaproxy.util.logging.Logger; -import javax.crypto.SecretKey; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.PublicKey; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; +import java.util.List; public class Proxy2ServerHandler extends SimpleChannelInboundHandler { private ProxyConnection proxyConnection; - private int joinGamePacketId = -1; - @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { super.channelRegistered(ctx); this.proxyConnection = ProxyConnection.fromChannel(ctx.channel()); - - this.joinGamePacketId = MCPackets.S2C_JOIN_GAME.getId(this.proxyConnection.getClientVersion().getVersion()); } @Override @@ -81,31 +55,13 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler { protected void channelRead0(ChannelHandlerContext ctx, IPacket packet) throws Exception { if (this.proxyConnection.isClosed()) return; - switch (this.proxyConnection.getConnectionState()) { - case LOGIN: - if (packet instanceof S2CLoginKeyPacket1_7) this.handleLoginKey((S2CLoginKeyPacket1_7) packet); - else if (packet instanceof S2CLoginSuccessPacket1_7) this.handleLoginSuccess((S2CLoginSuccessPacket1_7) packet); - else if (packet instanceof S2CLoginCompressionPacket) this.handleLoginCompression((S2CLoginCompressionPacket) packet); - else break; - + final List listeners = Lists.newArrayList(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + for (PacketHandler packetHandler : this.proxyConnection.getPacketHandlers()) { + if (!packetHandler.handleP2S(packet, listeners)) { return; - case STATUS: - if (packet instanceof S2CPingResponsePacket) { - this.proxyConnection.getC2P().writeAndFlush(packet).addListeners(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, ChannelFutureListener.CLOSE); - return; - } - break; - case PLAY: - final UnknownPacket unknownPacket = (UnknownPacket) packet; - if (unknownPacket.packetId == this.joinGamePacketId) { - this.proxyConnection.getC2P().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - this.sendResourcePack(); - return; - } - break; + } } - - this.proxyConnection.getC2P().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + this.proxyConnection.getC2P().writeAndFlush(packet).addListeners(listeners.toArray(new ChannelFutureListener[0])); } @Override @@ -113,95 +69,4 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler { ExceptionUtil.handleNettyException(ctx, cause, this.proxyConnection); } - private void handleLoginKey(final S2CLoginKeyPacket1_7 packet) throws InterruptedException, GeneralSecurityException, ExecutionException { - final PublicKey publicKey = CryptUtil.decodeRsaPublicKey(packet.publicKey); - final SecretKey secretKey = CryptUtil.generateSecretKey(); - final String serverHash = new BigInteger(CryptUtil.computeServerIdHash(packet.serverId, publicKey, secretKey)).toString(16); - - boolean auth = true; - if (this.proxyConnection.getServerVersion().isOlderThanOrEqualTo(VersionEnum.r1_6_4)) { - auth = this.proxyConnection.getUserConnection().get(ProtocolMetadataStorage.class).authenticate; - } - if (auth) { - ExternalInterface.joinServer(serverHash, this.proxyConnection); - } - - final byte[] encryptedSecretKey = CryptUtil.encryptData(publicKey, secretKey.getEncoded()); - final byte[] encryptedNonce = CryptUtil.encryptData(publicKey, packet.nonce); - - final C2SLoginKeyPacket1_19_3 loginKey = new C2SLoginKeyPacket1_19_3(encryptedSecretKey, encryptedNonce); - if (this.proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_19) && this.proxyConnection.getLoginHelloPacket() instanceof C2SLoginHelloPacket1_19 && ((C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket()).key != null) { - ExternalInterface.signNonce(packet.nonce, loginKey, this.proxyConnection); - } - this.proxyConnection.getChannel().writeAndFlush(loginKey).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - - if (this.proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) { - this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey)); - } else { - this.proxyConnection.setKeyForPreNettyEncryption(secretKey); - } - } - - private void handleLoginSuccess(final S2CLoginSuccessPacket1_7 packet) { - if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) { - if (Options.COMPRESSION_THRESHOLD > -1 && this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).get() == -1) { - this.proxyConnection.getChannel().config().setAutoRead(false); - this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListeners(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, (ChannelFutureListener) f -> { - if (f.isSuccess()) { - this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD); - this.proxyConnection.getChannel().config().setAutoRead(true); - } - }); - } - } - - this.proxyConnection.setGameProfile(new GameProfile(packet.uuid, packet.name)); - Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Connected successfully! Switching to PLAY state"); - - this.proxyConnection.getChannel().config().setAutoRead(false); - this.proxyConnection.getC2P().writeAndFlush(packet).addListeners(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, (ChannelFutureListener) f -> { - if (f.isSuccess()) { - this.proxyConnection.setConnectionState(ConnectionState.PLAY); - this.proxyConnection.getChannel().config().setAutoRead(true); - } - }); - } - - private void handleLoginCompression(final S2CLoginCompressionPacket packet) { - this.proxyConnection.getChannel().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(packet.compressionThreshold); - } - - private void sendResourcePack() { - if (Options.RESOURCE_PACK_URL != null) { - this.proxyConnection.getChannel().eventLoop().schedule(() -> { - if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) { - final ByteBuf resourcePackPacket = Unpooled.buffer(); - PacketTypes.writeVarInt(resourcePackPacket, MCPackets.S2C_RESOURCE_PACK.getId(this.proxyConnection.getClientVersion().getVersion())); - PacketTypes.writeString(resourcePackPacket, Options.RESOURCE_PACK_URL); // url - PacketTypes.writeString(resourcePackPacket, ""); // hash - if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_17)) { - resourcePackPacket.writeBoolean(Via.getConfig().isForcedUse1_17ResourcePack()); // required - final JsonElement promptMessage = Via.getConfig().get1_17ResourcePackPrompt(); - if (promptMessage != null) { - resourcePackPacket.writeBoolean(true); // has message - PacketTypes.writeString(resourcePackPacket, promptMessage.toString()); // message - } else { - resourcePackPacket.writeBoolean(false); // has message - } - } - this.proxyConnection.getC2P().writeAndFlush(resourcePackPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } else if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) { - final byte[] data = Options.RESOURCE_PACK_URL.getBytes(StandardCharsets.UTF_8); - - final ByteBuf customPayloadPacket = Unpooled.buffer(); - PacketTypes.writeVarInt(customPayloadPacket, MCPackets.S2C_PLUGIN_MESSAGE.getId(this.proxyConnection.getClientVersion().getVersion())); - PacketTypes.writeString(customPayloadPacket, "MC|RPack"); // channel - customPayloadPacket.writeShort(data.length); // length - customPayloadPacket.writeBytes(data); // data - this.proxyConnection.getC2P().writeAndFlush(customPayloadPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } - }, 250, TimeUnit.MILLISECONDS); - } - } - } diff --git a/src/main/java/net/raphimc/viaproxy/proxy/session/ProxyConnection.java b/src/main/java/net/raphimc/viaproxy/proxy/session/ProxyConnection.java index 56425f7..cac6858 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/session/ProxyConnection.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/session/ProxyConnection.java @@ -41,12 +41,15 @@ import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil; import net.raphimc.netminecraft.util.ServerAddress; import net.raphimc.vialoader.util.VersionEnum; import net.raphimc.viaproxy.proxy.external_interface.OpenAuthModConstants; +import net.raphimc.viaproxy.proxy.packethandler.PacketHandler; import net.raphimc.viaproxy.proxy.util.CloseAndReturn; import net.raphimc.viaproxy.util.logging.Logger; import java.security.GeneralSecurityException; import java.security.Key; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -59,6 +62,8 @@ public class ProxyConnection extends NetClient { public static final AttributeKey PROXY_CONNECTION_ATTRIBUTE_KEY = AttributeKey.valueOf("proxy_connection"); private final Channel c2p; + private final List packetHandlers = new ArrayList<>(); + private final AtomicInteger customPayloadId = new AtomicInteger(0); private final Map> customPayloadListener = new ConcurrentHashMap<>(); @@ -110,6 +115,10 @@ public class ProxyConnection extends NetClient { return this.c2p; } + public List getPacketHandlers() { + return this.packetHandlers; + } + public ServerAddress getServerAddress() { return this.serverAddress; } @@ -176,6 +185,11 @@ public class ProxyConnection extends NetClient { this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getLoginRegistry(true, this.clientVersion.getVersion())); this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getLoginRegistry(false, this.clientVersion.getVersion())); break; + case CONFIGURATION: + if (this.getChannel() != null) + this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getConfigurationRegistry(true, this.clientVersion.getVersion())); + this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getConfigurationRegistry(false, this.clientVersion.getVersion())); + break; case PLAY: if (this.getChannel() != null) this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getPlayRegistry(true, this.clientVersion.getVersion())); @@ -241,15 +255,20 @@ public class ProxyConnection extends NetClient { Logger.u_err("kick", this.c2p.remoteAddress(), this.getGameProfile(), message.replaceAll("§.", "")); final ChannelFuture future; - if (this.connectionState == ConnectionState.LOGIN) { + if (this.connectionState == ConnectionState.STATUS) { + future = this.c2p.writeAndFlush(new S2CStatusResponsePacket("{\"players\":{\"max\":0,\"online\":0},\"description\":" + new JsonPrimitive(message) + ",\"version\":{\"protocol\":-1,\"name\":\"ViaProxy\"}}")); + } else if (this.connectionState == ConnectionState.LOGIN) { future = this.c2p.writeAndFlush(new S2CLoginDisconnectPacket(messageToJson(message))); + } else if (this.connectionState == ConnectionState.CONFIGURATION) { + final ByteBuf disconnectPacket = Unpooled.buffer(); + PacketTypes.writeVarInt(disconnectPacket, MCPackets.S2C_CONFIG_DISCONNECT.getId(this.clientVersion.getVersion())); + PacketTypes.writeString(disconnectPacket, messageToJson(message)); + future = this.c2p.writeAndFlush(disconnectPacket); } else if (this.connectionState == ConnectionState.PLAY) { final ByteBuf disconnectPacket = Unpooled.buffer(); PacketTypes.writeVarInt(disconnectPacket, MCPackets.S2C_DISCONNECT.getId(this.clientVersion.getVersion())); PacketTypes.writeString(disconnectPacket, messageToJson(message)); future = this.c2p.writeAndFlush(disconnectPacket); - } else if (this.connectionState == ConnectionState.STATUS) { - future = this.c2p.writeAndFlush(new S2CStatusResponsePacket("{\"players\":{\"max\":0,\"online\":0},\"description\":" + new JsonPrimitive(message) + ",\"version\":{\"protocol\":-1,\"name\":\"ViaProxy\"}}")); } else { future = this.c2p.newSucceededFuture(); }