diff --git a/src/main/java/net/raphimc/viaproxy/cli/options/Options.java b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java index c1e8715..b521644 100644 --- a/src/main/java/net/raphimc/viaproxy/cli/options/Options.java +++ b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java @@ -34,24 +34,24 @@ public class Options { public static String BIND_ADDRESS = "0.0.0.0"; public static int BIND_PORT = 25568; - - public static boolean SRV_MODE; // Example: lenni0451.net_25565_1.8.x.viaproxy.127.0.0.1.nip.io - public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass - public static boolean ONLINE_MODE; - public static int NETTY_THREADS = 0; - public static int COMPRESSION_THRESHOLD = 256; - public static String CONNECT_ADDRESS; public static int CONNECT_PORT = 25565; public static VersionEnum PROTOCOL_VERSION; - + public static boolean ONLINE_MODE; public static boolean OPENAUTHMOD_AUTH; - public static boolean LOCAL_SOCKET_AUTH; public static boolean BETACRAFT_AUTH; // GUI only config options public static Account MC_ACCOUNT; + // CLI only config options + public static int NETTY_THREADS = 0; + public static int COMPRESSION_THRESHOLD = 256; + public static boolean SRV_MODE; // Example: lenni0451.net_25565_1.8.x.viaproxy.127.0.0.1.nip.io + public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass + public static boolean LOCAL_SOCKET_AUTH; + public static String RESOURCE_PACK_URL; + public static void parse(final String[] args) throws IOException { final OptionParser parser = new OptionParser(); final OptionSpec help = parser.acceptsAll(asList("help", "h", "?"), "Get a list of all arguments").forHelp(); @@ -69,6 +69,7 @@ public class Options { final OptionSpec openAuthModAuth = parser.acceptsAll(asList("openauthmod_auth", "oam_auth"), "Enable OpenAuthMod authentication"); final OptionSpec localSocketAuth = parser.accepts("local_socket_auth", "Enable authentication over a local socket"); final OptionSpec betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication servers for classic"); + final OptionSpec resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which all connecting clients can optionally download").withRequiredArg().ofType(String.class); final OptionSet options = parser.parse(args); if (options.has(help)) { @@ -94,6 +95,9 @@ public class Options { OPENAUTHMOD_AUTH = options.has(openAuthModAuth); LOCAL_SOCKET_AUTH = options.has(localSocketAuth); BETACRAFT_AUTH = options.has(betaCraftAuth); + if (options.has(resourcePackUrl)) { + RESOURCE_PACK_URL = options.valueOf(resourcePackUrl); + } } } 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 a0676a5..68a621a 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/proxy2server/Proxy2ServerHandler.java @@ -18,14 +18,21 @@ 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 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.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.storage.ProtocolMetadataStorage; import net.raphimc.viaprotocolhack.util.VersionEnum; @@ -37,19 +44,25 @@ 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; public class Proxy2ServerHandler extends SimpleChannelInboundHandler { private ProxyConnection proxyConnection; + private int joinGamePacketId = -1; + @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); this.proxyConnection = ProxyConnection.fromChannel(ctx.channel()); + + this.joinGamePacketId = MCPackets.S2C_JOIN_GAME.getId(this.proxyConnection.getClientVersion().getVersion()); } @Override @@ -75,6 +88,14 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler { else break; return; + 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); @@ -133,4 +154,37 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler { 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); + } + } + }