mirror of
https://github.com/ViaVersion/ViaProxy.git
synced 2024-11-28 12:55:53 +01:00
Added support for EaglerCraft clients
This commit is contained in:
parent
8da007c0a4
commit
4494920acd
@ -16,6 +16,8 @@ For a full user guide go to the [Usage for Players](#usage-for-players-gui) sect
|
|||||||
|
|
||||||
## Supported Client versions
|
## Supported Client versions
|
||||||
- Release (1.7.2 - 1.19.4)
|
- Release (1.7.2 - 1.19.4)
|
||||||
|
- Classic, Alpha, Beta, Release 1.0 - 1.6.4 (Only passthrough)
|
||||||
|
- Eaglercraft 1.8
|
||||||
|
|
||||||
ViaProxy supports joining to any of the listed server version from any of the listed client versions.
|
ViaProxy supports joining to any of the listed server version from any of the listed client versions.
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ public class Options {
|
|||||||
public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
|
public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
|
||||||
public static boolean SERVER_HAPROXY_PROTOCOL;
|
public static boolean SERVER_HAPROXY_PROTOCOL;
|
||||||
public static boolean LEGACY_CLIENT_PASSTHROUGH;
|
public static boolean LEGACY_CLIENT_PASSTHROUGH;
|
||||||
|
public static boolean ALLOW_EAGLERCRAFT_CLIENTS;
|
||||||
|
|
||||||
public static void parse(final String[] args) throws IOException {
|
public static void parse(final String[] args) throws IOException {
|
||||||
final OptionParser parser = new OptionParser();
|
final OptionParser parser = new OptionParser();
|
||||||
@ -79,7 +80,8 @@ public class Options {
|
|||||||
final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which clients can optionally download").withRequiredArg().ofType(String.class);
|
final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which clients can optionally download").withRequiredArg().ofType(String.class);
|
||||||
final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for backend TCP connections").withRequiredArg().ofType(String.class);
|
final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for backend TCP connections").withRequiredArg().ofType(String.class);
|
||||||
final OptionSpec<Void> serverHaProxyProtocol = parser.acceptsAll(asList("server-haproxy-protocol", "server-haproxy"), "Send HAProxy protocol messages to the backend server");
|
final OptionSpec<Void> serverHaProxyProtocol = parser.acceptsAll(asList("server-haproxy-protocol", "server-haproxy"), "Send HAProxy protocol messages to the backend server");
|
||||||
final OptionSpec<Void> legacyClientPassthrough = parser.acceptsAll(asList("legacy_client_passthrough", "legacy_passthrough"), "Allow <= 1.6.4 clients to connect to the backend server");
|
final OptionSpec<Void> legacyClientPassthrough = parser.acceptsAll(asList("legacy_client_passthrough", "legacy_passthrough"), "Allow <= 1.6.4 clients to connect to the backend server (No protocol translation)");
|
||||||
|
final OptionSpec<Void> allowEaglerCraftClients = parser.acceptsAll(asList("allow_eaglercraft_clients", "allow_eaglercraft"), "Allow EaglerCraft clients to connect to ViaProxy");
|
||||||
PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser));
|
PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser));
|
||||||
|
|
||||||
final OptionSet options = parser.parse(args);
|
final OptionSet options = parser.parse(args);
|
||||||
@ -119,6 +121,7 @@ public class Options {
|
|||||||
}
|
}
|
||||||
SERVER_HAPROXY_PROTOCOL = options.has(serverHaProxyProtocol);
|
SERVER_HAPROXY_PROTOCOL = options.has(serverHaProxyProtocol);
|
||||||
LEGACY_CLIENT_PASSTHROUGH = options.has(legacyClientPassthrough);
|
LEGACY_CLIENT_PASSTHROUGH = options.has(legacyClientPassthrough);
|
||||||
|
ALLOW_EAGLERCRAFT_CLIENTS = options.has(allowEaglerCraftClients);
|
||||||
PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
|
PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,11 +26,24 @@ import net.raphimc.viaproxy.cli.options.Options;
|
|||||||
import net.raphimc.viaproxy.plugins.PluginManager;
|
import net.raphimc.viaproxy.plugins.PluginManager;
|
||||||
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
|
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
|
||||||
import net.raphimc.viaproxy.plugins.events.types.ITyped;
|
import net.raphimc.viaproxy.plugins.events.types.ITyped;
|
||||||
|
import net.raphimc.viaproxy.proxy.client2proxy.eaglercraft.EaglercraftInitialHandler;
|
||||||
|
import net.raphimc.viaproxy.proxy.client2proxy.passthrough.LegacyClientPassthroughHandler;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer {
|
public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer {
|
||||||
|
|
||||||
|
public static final String EAGLERCRAFT_INITIAL_HANDLER_NAME = "eaglercraft-initial-handler";
|
||||||
|
public static final String WEBSOCKET_SSL_HANDLER_NAME = "ws-ssl-handler";
|
||||||
|
public static final String WEBSOCKET_HTTP_CODEC_NAME = "ws-http-codec";
|
||||||
|
public static final String WEBSOCKET_HTTP_AGGREGATOR_NAME = "ws-http-aggregator";
|
||||||
|
public static final String WEBSOCKET_COMPRESSION_NAME = "ws-compression";
|
||||||
|
public static final String WEBSOCKET_HANDLER_NAME = "ws-handler";
|
||||||
|
public static final String WEBSOCKET_ACTIVE_NOTIFIER_NAME = "ws-active-notifier";
|
||||||
|
public static final String EAGLERCRAFT_HANDLER_NAME = "eaglercraft-handler";
|
||||||
|
|
||||||
|
public static final String LEGACY_PASSTHROUGH_HANDLER_NAME = "legacy-passthrough-handler";
|
||||||
|
|
||||||
public Client2ProxyChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
|
public Client2ProxyChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
|
||||||
super(handlerSupplier);
|
super(handlerSupplier);
|
||||||
}
|
}
|
||||||
@ -42,9 +55,13 @@ public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Options.LEGACY_CLIENT_PASSTHROUGH) {
|
if (Options.ALLOW_EAGLERCRAFT_CLIENTS) {
|
||||||
channel.pipeline().addLast("legacy-passthrough-handler", new LegacyClientPassthroughHandler());
|
channel.pipeline().addLast(EAGLERCRAFT_INITIAL_HANDLER_NAME, new EaglercraftInitialHandler());
|
||||||
}
|
}
|
||||||
|
if (Options.LEGACY_CLIENT_PASSTHROUGH) {
|
||||||
|
channel.pipeline().addLast(LEGACY_PASSTHROUGH_HANDLER_NAME, new LegacyClientPassthroughHandler());
|
||||||
|
}
|
||||||
|
|
||||||
super.initChannel(channel);
|
super.initChannel(channel);
|
||||||
channel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(false));
|
channel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(false));
|
||||||
|
|
||||||
|
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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.client2proxy.eaglercraft;
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.MessageToMessageCodec;
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
import net.raphimc.netminecraft.constants.ConnectionState;
|
||||||
|
import net.raphimc.netminecraft.constants.MCPackets;
|
||||||
|
import net.raphimc.netminecraft.constants.MCPipeline;
|
||||||
|
import net.raphimc.netminecraft.packet.PacketTypes;
|
||||||
|
import net.raphimc.viaproxy.ViaProxy;
|
||||||
|
import net.raphimc.viaproxy.util.logging.Logger;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
// Thanks to ayunami2000 for helping with the eaglercraft protocol
|
||||||
|
public class EaglercraftHandler extends MessageToMessageCodec<WebSocketFrame, ByteBuf> {
|
||||||
|
|
||||||
|
private static final int CLIENT_VERSION = 0x01;
|
||||||
|
private static final int SERVER_VERSION = 0x02;
|
||||||
|
private static final int VERSION_MISMATCH = 0x03;
|
||||||
|
private static final int CLIENT_REQUEST_LOGIN = 0x04;
|
||||||
|
private static final int SERVER_ALLOW_LOGIN = 0x05;
|
||||||
|
private static final int SERVER_DENY_LOGIN = 0x06;
|
||||||
|
private static final int CLIENT_PROFILE_DATA = 0x07;
|
||||||
|
private static final int CLIENT_FINISH_LOGIN = 0x08;
|
||||||
|
private static final int SERVER_FINISH_LOGIN = 0x09;
|
||||||
|
private static final int SERVER_ERROR = 0xFF;
|
||||||
|
|
||||||
|
private HostAndPort host;
|
||||||
|
private State state = State.PRE_HANDSHAKE;
|
||||||
|
private int protocolVersion;
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ctx.attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(-2); // Disable automatic compression in Proxy2ServerHandler
|
||||||
|
ctx.pipeline().remove(MCPipeline.SIZER_HANDLER_NAME);
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
|
if (this.state != State.LOGIN_COMPLETE) {
|
||||||
|
throw new IllegalStateException("Cannot send packets before login is completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.add(new BinaryWebSocketFrame(in.retain()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame in, List<Object> out) {
|
||||||
|
if (in instanceof BinaryWebSocketFrame) {
|
||||||
|
final ByteBuf data = in.content();
|
||||||
|
|
||||||
|
switch (this.state) {
|
||||||
|
case PRE_HANDSHAKE:
|
||||||
|
if (data.readableBytes() >= 2) { // Check for legacy client
|
||||||
|
if (data.getByte(0) == (byte) 2 && data.getByte(1) == (byte) 69) {
|
||||||
|
throw new IllegalStateException("Your client is not yet supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.state = State.HANDSHAKE;
|
||||||
|
case HANDSHAKE:
|
||||||
|
int packetId = data.readUnsignedByte(); // packet id
|
||||||
|
if (packetId == CLIENT_VERSION) {
|
||||||
|
int eaglercraftVersion = data.readUnsignedByte(); // eaglercraft version
|
||||||
|
final int minecraftVersion;
|
||||||
|
if (eaglercraftVersion == 1) {
|
||||||
|
minecraftVersion = data.readUnsignedByte(); // minecraft version
|
||||||
|
} else if (eaglercraftVersion == 2) {
|
||||||
|
int count = data.readUnsignedShort(); // eaglercraft versions
|
||||||
|
final List<Integer> eaglercraftVersions = new ArrayList<>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
eaglercraftVersions.add(data.readUnsignedShort()); // eaglercraft version id
|
||||||
|
}
|
||||||
|
if (!eaglercraftVersions.contains(2) && !eaglercraftVersions.contains(3)) {
|
||||||
|
Logger.LOGGER.error("No supported eaglercraft versions found");
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eaglercraftVersions.contains(3)) {
|
||||||
|
eaglercraftVersion = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = data.readUnsignedShort(); // minecraft versions
|
||||||
|
final List<Integer> minecraftVersions = new ArrayList<>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
minecraftVersions.add(data.readUnsignedShort()); // minecraft version id
|
||||||
|
}
|
||||||
|
if (minecraftVersions.size() != 1) {
|
||||||
|
Logger.LOGGER.error("No supported minecraft versions found");
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
minecraftVersion = minecraftVersions.get(0);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown Eaglercraft version: " + eaglercraftVersion);
|
||||||
|
}
|
||||||
|
final String clientBrand = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // client brand
|
||||||
|
final String clientVersionString = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // client version
|
||||||
|
|
||||||
|
if (eaglercraftVersion >= 2) {
|
||||||
|
data.skipBytes(1); // auth enabled
|
||||||
|
data.skipBytes(data.readUnsignedByte()); // auth username
|
||||||
|
}
|
||||||
|
if (data.isReadable()) {
|
||||||
|
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.HANDSHAKE_COMPLETE;
|
||||||
|
this.protocolVersion = minecraftVersion;
|
||||||
|
Logger.LOGGER.info("Eaglercraft client connected: " + clientBrand + " " + clientVersionString);
|
||||||
|
final String serverBrand = "ViaProxy";
|
||||||
|
final String serverVersionString = ViaProxy.VERSION;
|
||||||
|
|
||||||
|
final ByteBuf response = ctx.alloc().buffer();
|
||||||
|
response.writeByte(SERVER_VERSION); // packet id
|
||||||
|
if (eaglercraftVersion == 1) {
|
||||||
|
response.writeByte(1); // eaglercraft version
|
||||||
|
} else {
|
||||||
|
response.writeShort(eaglercraftVersion); // eaglercraft version
|
||||||
|
response.writeShort(minecraftVersion); // minecraft version
|
||||||
|
}
|
||||||
|
response.writeByte(serverBrand.length()).writeCharSequence(serverBrand, StandardCharsets.US_ASCII); // server brand
|
||||||
|
response.writeByte(serverVersionString.length()).writeCharSequence(serverVersionString, StandardCharsets.US_ASCII); // server version
|
||||||
|
response.writeByte(0); // auth method
|
||||||
|
response.writeShort(0); // salt length
|
||||||
|
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HANDSHAKE_COMPLETE:
|
||||||
|
packetId = data.readUnsignedByte(); // packet id
|
||||||
|
if (packetId == CLIENT_REQUEST_LOGIN) {
|
||||||
|
final String username = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // username
|
||||||
|
data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // requested server
|
||||||
|
data.skipBytes(data.readUnsignedByte()); // auth password
|
||||||
|
if (data.isReadable()) {
|
||||||
|
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.LOGIN;
|
||||||
|
this.username = username;
|
||||||
|
final UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
final ByteBuf response = ctx.alloc().buffer();
|
||||||
|
response.writeByte(SERVER_ALLOW_LOGIN); // packet id
|
||||||
|
response.writeByte(username.length()).writeCharSequence(username, StandardCharsets.US_ASCII); // username
|
||||||
|
response.writeLong(uuid.getMostSignificantBits()).writeLong(uuid.getLeastSignificantBits()); // uuid
|
||||||
|
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOGIN:
|
||||||
|
packetId = data.readUnsignedByte(); // packet id
|
||||||
|
if (packetId == CLIENT_PROFILE_DATA) {
|
||||||
|
final String type = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
|
||||||
|
final byte[] dataBytes = new byte[data.readUnsignedShort()];
|
||||||
|
data.readBytes(dataBytes);
|
||||||
|
if (data.isReadable()) {
|
||||||
|
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
|
||||||
|
}
|
||||||
|
} else if (packetId == CLIENT_FINISH_LOGIN) {
|
||||||
|
if (data.isReadable()) {
|
||||||
|
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
|
||||||
|
}
|
||||||
|
this.state = State.LOGIN_COMPLETE;
|
||||||
|
|
||||||
|
final int handshakeId = MCPackets.C2S_HANDSHAKE.getId(this.protocolVersion);
|
||||||
|
final int loginHelloId = MCPackets.C2S_LOGIN_HELLO.getId(this.protocolVersion);
|
||||||
|
|
||||||
|
if (handshakeId == -1 || loginHelloId == -1) {
|
||||||
|
Logger.LOGGER.error("Unsupported protocol version: " + this.protocolVersion);
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteBuf handshake = ctx.alloc().buffer();
|
||||||
|
PacketTypes.writeVarInt(handshake, handshakeId); // packet id
|
||||||
|
PacketTypes.writeVarInt(handshake, this.protocolVersion); // protocol version
|
||||||
|
PacketTypes.writeString(handshake, this.host.getHost()); // address
|
||||||
|
handshake.writeShort(this.host.getPort()); // port
|
||||||
|
PacketTypes.writeVarInt(handshake, ConnectionState.LOGIN.getId()); // next state
|
||||||
|
out.add(handshake);
|
||||||
|
|
||||||
|
final ByteBuf loginHello = ctx.alloc().buffer();
|
||||||
|
PacketTypes.writeVarInt(loginHello, loginHelloId); // packet id
|
||||||
|
PacketTypes.writeString(loginHello, this.username); // username
|
||||||
|
out.add(loginHello);
|
||||||
|
|
||||||
|
final ByteBuf response = ctx.alloc().buffer();
|
||||||
|
response.writeByte(SERVER_FINISH_LOGIN); // packet id
|
||||||
|
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOGIN_COMPLETE:
|
||||||
|
out.add(data.retain());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected binary frame in state " + this.state);
|
||||||
|
}
|
||||||
|
} else if (in instanceof TextWebSocketFrame) {
|
||||||
|
final String text = ((TextWebSocketFrame) in).text();
|
||||||
|
ctx.close();
|
||||||
|
|
||||||
|
if (this.state != State.PRE_HANDSHAKE) {
|
||||||
|
throw new IllegalStateException("Unexpected text frame in state " + this.state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Unsupported frame type: " + in.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
|
||||||
|
final WebSocketServerProtocolHandler.HandshakeComplete handshake = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
|
||||||
|
if (!handshake.requestHeaders().contains("Host")) {
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.host = HostAndPort.fromString(handshake.requestHeaders().get("Host")).withDefaultPort(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.userEventTriggered(ctx, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
PRE_HANDSHAKE,
|
||||||
|
HANDSHAKE,
|
||||||
|
HANDSHAKE_COMPLETE,
|
||||||
|
LOGIN,
|
||||||
|
LOGIN_COMPLETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.client2proxy.eaglercraft;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||||
|
import io.netty.handler.codec.http.HttpServerCodec;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EaglercraftInitialHandler extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
private static SslContext sslContext;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final File certFolder = new File("certs");
|
||||||
|
if (certFolder.exists()) {
|
||||||
|
try {
|
||||||
|
sslContext = SslContextBuilder.forServer(new File(certFolder, "fullchain.pem"), new File(certFolder, "privkey.pem")).build();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException("Failed to load SSL context", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
|
if (!ctx.channel().isOpen()) return;
|
||||||
|
if (!in.isReadable()) return;
|
||||||
|
|
||||||
|
if (in.readableBytes() >= 3) {
|
||||||
|
final byte[] data = new byte[3];
|
||||||
|
in.getBytes(0, data);
|
||||||
|
final String method = new String(data, StandardCharsets.UTF_8);
|
||||||
|
if (method.equals("GET")) { // Websocket request
|
||||||
|
if (sslContext != null) {
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_SSL_HANDLER_NAME, sslContext.newHandler(ctx.alloc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HTTP_CODEC_NAME, new HttpServerCodec());
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HTTP_AGGREGATOR_NAME, new HttpObjectAggregator(65535, true));
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_COMPRESSION_NAME, new WebSocketServerCompressionHandler());
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HANDLER_NAME, new WebSocketServerProtocolHandler("/", null, true));
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_ACTIVE_NOTIFIER_NAME, new WebSocketActiveNotifier());
|
||||||
|
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.EAGLERCRAFT_HANDLER_NAME, new EaglercraftHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pipeline().remove(this);
|
||||||
|
ctx.pipeline().fireChannelRead(in.retain());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.client2proxy.eaglercraft;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
|
||||||
|
public class WebSocketActiveNotifier extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
|
||||||
|
ctx.fireChannelActive();
|
||||||
|
ctx.pipeline().remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.userEventTriggered(ctx, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package net.raphimc.viaproxy.proxy.client2proxy;
|
package net.raphimc.viaproxy.proxy.client2proxy.passthrough;
|
||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
@ -45,6 +45,9 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer {
|
public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer {
|
||||||
|
|
||||||
|
public static final String VIAPROXY_PROXY_HANDLER_NAME = "viaproxy-proxy-handler";
|
||||||
|
public static final String VIAPROXY_HAPROXY_ENCODER_NAME = "viaproxy-haproxy-encoder";
|
||||||
|
|
||||||
public Proxy2ServerChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
|
public Proxy2ServerChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
|
||||||
super(handlerSupplier);
|
super(handlerSupplier);
|
||||||
}
|
}
|
||||||
@ -62,10 +65,10 @@ public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer
|
|||||||
proxyConnection.setUserConnection(user);
|
proxyConnection.setUserConnection(user);
|
||||||
|
|
||||||
if (Options.PROXY_URL != null && !proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest)) {
|
if (Options.PROXY_URL != null && !proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest)) {
|
||||||
channel.pipeline().addLast("viaproxy-proxy-handler", this.getProxyHandler());
|
channel.pipeline().addLast(VIAPROXY_PROXY_HANDLER_NAME, this.getProxyHandler());
|
||||||
}
|
}
|
||||||
if (Options.SERVER_HAPROXY_PROTOCOL) {
|
if (Options.SERVER_HAPROXY_PROTOCOL) {
|
||||||
channel.pipeline().addLast("viaproxy-haproxy-encoder", HAProxyMessageEncoder.INSTANCE);
|
channel.pipeline().addLast(VIAPROXY_HAPROXY_ENCODER_NAME, HAProxyMessageEncoder.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initChannel(channel);
|
super.initChannel(channel);
|
||||||
|
@ -137,7 +137,7 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler<IPacket> {
|
|||||||
|
|
||||||
private void handleLoginSuccess(final S2CLoginSuccessPacket1_7 packet) throws Exception {
|
private void handleLoginSuccess(final S2CLoginSuccessPacket1_7 packet) throws Exception {
|
||||||
if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) {
|
if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) {
|
||||||
if (Options.COMPRESSION_THRESHOLD > -1) {
|
if (Options.COMPRESSION_THRESHOLD > -1 && this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).get() == -1) {
|
||||||
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).await();
|
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).await();
|
||||||
this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD);
|
this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user