Added support for 23w32a clients and servers, Refactored packet handler code

This commit is contained in:
RaphiMC 2023-08-15 21:22:18 +02:00
parent 512ce30ffe
commit 94baa38e7f
No known key found for this signature in database
GPG Key ID: 0F6BB0657A03AC94
15 changed files with 710 additions and 299 deletions

View File

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

View File

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

View File

@ -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 = "<clinit>", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;register(ILjava/lang/String;)Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;"))

View File

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

View File

@ -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<IPacket> {
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<IPacket> {
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<ChannelFutureListener> 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<IPacket> {
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<IPacket> {
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<IPacket> {
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ChannelFutureListener> listeners) throws Exception {
return true;
}
public boolean handleP2S(final IPacket packet, final List<ChannelFutureListener> listeners) throws Exception {
return true;
}
}

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ChannelFutureListener> listeners) {
if (packet instanceof S2CStatusPongPacket) {
listeners.add(ChannelFutureListener.CLOSE);
}
return true;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ChannelFutureListener> listeners) {
final ConnectionState connectionState = this.proxyConnection.getConnectionState();
if (connectionState.equals(ConnectionState.HANDSHAKING)) {
throw new IllegalStateException("Unexpected packet in " + connectionState + " state");
}
return true;
}
}

View File

@ -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<IPacket> {
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<IPacket> {
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<ChannelFutureListener> 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<IPacket> {
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);
}
}
}

View File

@ -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<ProxyConnection> PROXY_CONNECTION_ATTRIBUTE_KEY = AttributeKey.valueOf("proxy_connection");
private final Channel c2p;
private final List<PacketHandler> packetHandlers = new ArrayList<>();
private final AtomicInteger customPayloadId = new AtomicInteger(0);
private final Map<Integer, CompletableFuture<ByteBuf>> customPayloadListener = new ConcurrentHashMap<>();
@ -110,6 +115,10 @@ public class ProxyConnection extends NetClient {
return this.c2p;
}
public List<PacketHandler> 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();
}