https://vibecoding-ai.net/ 1000 символов за раз Я хочу написать свой собственный AnyProxy проект на подобии ViaProxy (ViaVersion) для поддержки захода со старых версий клиента на сервера с последними версиями, и наоборот с новых версий клиента на сервера со старыми версиями, AnyProxy\src\main\java\ru\vbproduction667\anyproxy\AnyProxy.java путь к основному файлу в моём проекте, хочу использовать https://github.com/CloudburstMC/Protocol где есть уже поддержка протоколов от 291 версия 1.7 до последней версии 859 1.21.124, пример импорта кодеков import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859;, https://github.com/CloudburstMC/Network RakNet составляющая используемая в https://github.com/CloudburstMC/ProxyPass, маппинги возьму из репозиториев Nukkit, GeyserMC и PocketMine-MP, важно не используй устаревшие пути com.nukkitx. они уже давно заменены на org.cloudburstmc., AnyProxy\src\main\java\ru\vbproduction667\anyproxy\network\protocol мой кастомный путь, напиши все нужные и
Отличная идея для проекта! Создание такого AnyProxy — амбициозная, но очень полезная задача. Ниже я постараюсь максимально полно ответить на ваш запрос, учитывая все указанные вами детали.
Структура проекта и основные классы:
Ваш файл AnyProxy.java будет точкой входа. В нем необходимо инициализировать прокси-сервер, который будет слушать входящие соединения от клиентов и устанавливать исходящие соединения с целевыми серверами.
// AnyProxy/src/main/java/ru/vbproduction667/anyproxy/AnyProxy.java
package ru.vbproduction667.anyproxy;
import lombok.extern.slf4j.Slf4j;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859; // Пример импорта кодека
import org.cloudburstmc.protocol.bedrock.netty.BedrockCipher;
import org.cloudburstmc.protocol.bedrock.netty.BedrockRakNetServerInitializer;
import org.cloudburstmc.protocol.bedrock.netty.BedrockServerChannel;
import org.cloudburstmc.protocol.bedrock.netty.codec.frame.BedrockFrameDecoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.frame.BedrockFrameEncoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketDecoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketEncoder;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.util.BedrockVersion;
import org.cloudburstmc.raknet.RakNetServer;
import org.cloudburstmc.raknet.pipeline.RakNetDatagramCodec;
import org.cloudburstmc.raknet.pipeline.RakNetPacketCodec;
import org.cloudburstmc.raknet.protocol.RakNetPacket;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j // Использование Lombok для логирования
public class AnyProxy {
private final String bindAddress;
private final int bindPort;
private final String remoteAddress;
private final int remotePort;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
// Маппинг для хранения активных прокси-сессий (клиент-сервер)
private final Map<InetSocketAddress, ProxySession> activeSessions = new ConcurrentHashMap<>();
public AnyProxy(String bindAddress, int bindPort, String remoteAddress, int remotePort) {
this.bindAddress = bindAddress;
this.bindPort = bindPort;
this.remoteAddress = remoteAddress;
this.remotePort = remotePort;
}
public void start() throws InterruptedException {
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
try {
// Создаем RakNetServer для прослушивания входящих соединений от клиентов
RakNetServer rakNetServer = RakNetServer.builder()
.address(new InetSocketAddress(bindAddress, bindPort))
.eventLoopGroup(bossGroup) // Используем bossGroup для RakNet
.maximumConnections(Integer.MAX_VALUE)
.build();
// Создаем Netty ServerBootstrap для обработки Bedrock протокола
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) // bossGroup для принятия соединений, workerGroup для обработки
.channel(NioDatagramChannel.class) // UDP канал для Bedrock
.option(ChannelOption.SO_BROADCAST, true)
.handler(rakNetServer.getInitializer()) // Инициализируем RakNetServer
.childHandler(new ChannelInitializer<DatagramChannel>() {
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
// Здесь будет основной Bedrock протокол
ch.pipeline().addLast(new BedrockRakNetServerInitializer() {
@Override
protected void initSession(Channel sessionChannel) {
// Когда RakNet устанавливает сессию, мы добавляем Bedrock обработчики
sessionChannel.pipeline()
.addLast(new BedrockFrameDecoder()) // Декодирует фреймы Bedrock
.addLast(new BedrockFrameEncoder()) // Кодирует фреймы Bedrock
.addLast(new BedrockPacketDecoder(Bedrock_v859.CODEC)) // Декодирует пакеты Bedrock
.addLast(new BedrockPacketEncoder(Bedrock_v859.CODEC)) // Кодирует пакеты Bedrock
.addLast(new ClientConnectionHandler(AnyProxy.this, sessionChannel)); // Ваш обработчик клиентских соединений
}
});
}
});
ChannelFuture future = b.bind(bindAddress, bindPort).sync();
log.info("AnyProxy запущен на {}:{}, перенаправляет на {}:{}", bindAddress, bindPort, remoteAddress, remotePort);
future.channel().closeFuture().sync();
} finally {
shutdown();
}
}
public void shutdown() {
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
log.info("AnyProxy остановлен.");
}
public static void main(String[] args) throws InterruptedException {
// Пример использования
String bindAddress = "0.0.0.0"; // Адрес, на котором прокси будет слушать клиентов
int bindPort = 19132; // Порт, на котором прокси будет слушать клиентов
String remoteAddress = "127.0.0.1"; // Адрес целевого сервера
int remotePort = 19133; // Порт целевого сервера
AnyProxy proxy = new AnyProxy(bindAddress, bindPort, remoteAddress, remotePort);
proxy.start();
}
/**
* Обработчик входящих соединений от клиентов.
*/
private static class ClientConnectionHandler extends ChannelInboundHandlerAdapter {
private final AnyProxy parent;
private final Channel clientChannel;
private ProxySession session;
public ClientConnectionHandler(AnyProxy parent, Channel clientChannel) {
this.parent = parent;
this.clientChannel = clientChannel;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress clientAddress = (InetSocketAddress) ctx.channel().remoteAddress();
log.info("Новое входящее соединение от клиента: {}", clientAddress);
// Создаем новую прокси-сессию для этого клиента
session = new ProxySession(parent, clientChannel, parent.remoteAddress, parent.remotePort);
parent.activeSessions.put(clientAddress, session);
// Запускаем подключение к удаленному серверу
session.connectToServer().addListener(future -> {
if (future.isSuccess()) {
log.info("Успешно подключено к удаленному серверу для клиента {}", clientAddress);
} else {
log.error("Не удалось подключиться к удаленному серверу для клиента {}: {}", clientAddress, future.cause().getMessage());
ctx.close(); // Закрываем клиентское соединение, если не удалось подключиться к серверу
}
});
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof BedrockPacket) {
BedrockPacket packet = (BedrockPacket) msg;
// Здесь будет логика обработки пакетов от клиента, их модификация и пересылка на сервер
// В зависимости от версии клиента и версии сервера, пакеты будут транслироваться
// Для начала просто перешлем пакет, а потом добавим логику трансляции
if (session != null && session.getServerChannel() != null) {
session.getServerChannel().writeAndFlush(packet);
}
} else {
log.warn("Получен неизвестный объект от клиента: {}", msg.getClass().getName());
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress clientAddress = (InetSocketAddress) ctx.channel().remoteAddress();
log.info("Соединение с клиентом {} разорвано.", clientAddress);
if (session != null) {
session.disconnect();
parent.activeSessions.remove(clientAddress);
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Ошибка в обработчике клиента {}: {}", ctx.channel().remoteAddress(), cause.getMessage());
ctx.close();
}
}
}
AnyProxy/src/main/java/ru/vbproduction667/anyproxy/network/protocol/ProxySession.java (Новый файл):
// AnyProxy/src/main/java/ru/vbproduction667/anyproxy/network/protocol/ProxySession.java
package ru.vbproduction667.anyproxy.network.protocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859; // Пример импорта кодека
import org.cloudburstmc.protocol.bedrock.netty.BedrockRakNetClientInitializer;
import org.cloudburstmc.protocol.bedrock.netty.codec.frame.BedrockFrameDecoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.frame.BedrockFrameEncoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketDecoder;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketEncoder;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.raknet.RakNetClient;
import org.cloudburstmc.raknet.pipeline.RakNetDatagramCodec;
import org.cloudburstmc.raknet.pipeline.RakNetPacketCodec;
import ru.vbproduction667.anyproxy.AnyProxy;
import java.net.InetSocketAddress;
@Slf4j
public class ProxySession {
private final AnyProxy parent;
private final Channel clientChannel; // Канал клиента
private final String remoteAddress;
private final int remotePort;
@Getter
private Channel serverChannel; // Канал к удаленному серверу
private EventLoopGroup workerGroup; // Отдельный EventLoopGroup для клиентских соединений к серверу
public ProxySession(AnyProxy parent, Channel clientChannel, String remoteAddress, int remotePort) {
this.parent = parent;
this.clientChannel = clientChannel;
this.remoteAddress = remoteAddress;
this.remotePort = remotePort;
this.workerGroup = new NioEventLoopGroup(1); // Используем отдельный workerGroup для каждого сессии
}
public ChannelFuture connectToServer() {
// Создаем RakNetClient для подключения к удаленному серверу
RakNetClient rakNetClient = RakNetClient.builder()
.address(new InetSocketAddress(remoteAddress, remotePort))
.eventLoopGroup(workerGroup) // Используем workerGroup для RakNet клиента
.build();
Bootstrap b = new Bootstrap();
b.group(workerGroup)
.channel(NioDatagramChannel.class) // UDP канал для Bedrock
.handler(rakNetClient.getInitializer()) // Инициализируем RakNetClient
.remoteAddress(remoteAddress, remotePort) // Указываем удаленный адрес
.handler(new ChannelInitializer<DatagramChannel>() { // Child handler для Bedrock протокола
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
// Здесь будет основной Bedrock протокол для подключения к серверу
ch.pipeline().addLast(new BedrockRakNetClientInitializer() {
@Override
protected void initSession(Channel sessionChannel) {
// Когда RakNet устанавливает сессию, мы добавляем Bedrock обработчики
sessionChannel.pipeline()
.addLast(new BedrockFrameDecoder())
.addLast(new BedrockFrameEncoder())
.addLast(new BedrockPacketDecoder(Bedrock_v859.CODEC)) // Декодирует пакеты от сервера
.addLast(new BedrockPacketEncoder(Bedrock_v859.CODEC)) // Кодирует пакеты для сервера
.addLast(new ServerConnectionHandler(ProxySession.this, clientChannel)); // Ваш обработчик серверных соединений
serverChannel = sessionChannel; // Сохраняем канал к серверу
}
});
}
});
return b.connect();
}
public void disconnect() {
if (clientChannel != null && clientChannel.isOpen()) {
clientChannel.close();
}
if (serverChannel != null && serverChannel.isOpen()) {
serverChannel.close();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
log.info("Сессия с клиентом {} и сервером {} разорвана.", clientChannel.remoteAddress(), (serverChannel != null ? serverChannel.remoteAddress() : "N/A"));
}
/**
* Обработчик входящих пакетов от удаленного сервера.
*/
private static class ServerConnectionHandler extends ChannelInboundHandlerAdapter {
private final ProxySession parent;
private final Channel clientChannel;
public ServerConnectionHandler(ProxySession parent, Channel clientChannel) {
this.parent = parent;
this.clientChannel = clientChannel;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof BedrockPacket) {
BedrockPacket packet = (BedrockPacket) msg;
// Здесь будет логика обработки пакетов от сервера, их модификация и пересылка клиенту
// Для начала просто перешлем пакет
if (clientChannel != null && clientChannel.isOpen()) {
clientChannel.writeAndFlush(packet);
}
} else {
log.warn("Получен неизвестный объект от сервера: {}", msg.getClass().getName());
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("Соединение с удаленным сервером разорвано.");
parent.disconnect(); // Отключаем всю сессию при разрыве соединения с сервером
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Ошибка в обработчике сервера {}: {}", ctx.channel().remoteAddress(), cause.getMessage());
ctx.close();
parent.disconnect();
}
}
}
Пояснения и дальнейшие шаги:
Зависимости Maven/Gradle:
Вам понадобятся следующие зависимости в вашем pom.xml (Maven) или build.gradle (Gradle):
org.cloudburstmc.protocol:bedrock-protocol:<latest-version> (например, 1.2.0-SNAPSHOT или стабильная, если есть)org.cloudburstmc.protocol:bedrock-codec-vXXX:<latest-version> (для каждого кодека, который вы хотите поддерживать, например, bedrock-codec-v859, bedrock-codec-v291, и т.д.)org.cloudburstmc:raknet:<latest-version>io.netty:netty-all:<latest-version> (или отдельные модули Netty)org.projectlombok:lombok:<latest-version> (для @Slf4j, @Getter и т.д.)org.slf4j:slf4j-api:<latest-version>ch.qos.logback:logback-classic:<latest-version> (или другая реализация SLF4J для логирования)Пример pom.xml (частично):
<dependencies>
<dependency>
<groupId>org.cloudburstmc.protocol</groupId>
<artifactId>bedrock-protocol</artifactId>
<version>1.2.0-SNAPSHOT</version> <!-- Проверьте актуальную версию -->
</dependency>
<dependency>
<groupId>org.cloudburstmc.protocol</groupId>
<artifactId>bedrock-codec-v859</artifactId>
<version>1.2.0-SNAPSHOT</version>
</dependency>
<!-- Добавьте другие кодеки по мере необходимости -->
<dependency>
<groupId>org.cloudburstmc</groupId>
<artifactId>raknet</artifactId>
<version>1.2.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.109.Final</version> <!-- Проверьте актуальную версию -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
</dependencies>
Основная логика трансляции пакетов (Packet Translation):
Это самая сложная часть. Вам понадобится:
LoginPacket), вы сможете определить версию клиента. Затем вам нужно будет установить соединение с сервером и определить его версию (возможно, по ответу на пинг или по первому PlayStatusPacket).BedrockPacketDecoder и BedrockPacketEncoder вам нужно будет использовать соответствующий BedrockCodec для версии клиента и сервера. Это потребует создания маппинга версия -> BedrockCodec.ClientboundPacketTranslator и ServerboundPacketTranslator.Пример динамического выбора кодека (псевдокод):
В ClientConnectionHandler и ServerConnectionHandler вам нужно будет определить версию и затем использовать соответствующий кодек:
// ... в ClientConnectionHandler initSession
BedrockCodec clientCodec = getCodecForVersion(clientVersion); // Нужна функция для получения кодека
sessionChannel.pipeline()
.addLast(new BedrockPacketDecoder(clientCodec))
.addLast(new BedrockPacketEncoder(clientCodec))
// ...
// ... в ProxySession connectToServer initSession
BedrockCodec serverCodec = getCodecForVersion(serverVersion); // Нужна функция для получения кодека
sessionChannel.pipeline()
.addLast(new BedrockPacketDecoder(serverCodec))
.addLast(new BedrockPacketEncoder(serverCodec))
// ...
Вам понадобится статическая карта Map<Integer, BedrockCodec> для хранения всех поддерживаемых кодеков.
Обработка состояний (логин, игра): Протокол Bedrock имеет разные состояния (например, логин, игра). Вам нужно будет отслеживать текущее состояние для каждого клиента, чтобы правильно обрабатывать пакеты.
Использование org.cloudburstmc.protocol:
BedrockPacket - базовый класс для всех пакетов Bedrock.BedrockPacketType - enum для типов пакетов.Bedrock_v859) содержит методы для чтения и записи пакетов.BedrockRakNetServerInitializer и BedrockRakNetClientInitializer упрощают интеграцию RakNet с Netty.Маппинги сущностей, блоков, предметов: Помимо пакетов, вам также придется маппировать ID сущностей, блоков, предметов, так как они могут меняться между версиями. Это обычно реализуется через JSON-файлы или хардкодированные карты.
Конфигурация: Добавьте возможность конфигурировать адрес прокси, целевой сервер и, возможно, список поддерживаемых версий.
Тестирование: Это будет критически важно. Вам понадобятся клиенты разных версий и сервера разных версий для тща