Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 22:40:18 +01:00
Merge remote-tracking branch 'upstream/master' into feature/blocky
Dieser Commit ist enthalten in:
Commit
bb6e2e475a
10
Jenkinsfile
vendored
10
Jenkinsfile
vendored
@ -25,14 +25,4 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
|
||||||
success {
|
|
||||||
script {
|
|
||||||
if (env.BRANCH_NAME == 'master') {
|
|
||||||
build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
4
LICENSE
4
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License
|
The MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||||||
|
|
||||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||||
|
|
||||||
### Currently supporting Minecraft Bedrock 1.19.40 - 1.19.80 and Minecraft Java 1.19.4.
|
### Currently supporting Minecraft Bedrock 1.19.40 - 1.19.81 and Minecraft Java 1.19.4.
|
||||||
|
|
||||||
## Setting Up
|
## Setting Up
|
||||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.event.connection;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.index.qual.NonNegative;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.geysermc.event.Event;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever Geyser gets pinged
|
||||||
|
*
|
||||||
|
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online,
|
||||||
|
* Geyser will reply to the client with what was given.
|
||||||
|
*/
|
||||||
|
public interface GeyserBedrockPingEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given string as the primary motd, the given string cannot be null.
|
||||||
|
*
|
||||||
|
* @param primary the string to set as the primary motd
|
||||||
|
*/
|
||||||
|
void primaryMotd(@NonNull String primary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given string as the secondary motd, the given string cannot be null.
|
||||||
|
* Note: the secondary motd is only used for the LAN game entry.
|
||||||
|
*
|
||||||
|
* @param secondary the string to set as the secondary motd
|
||||||
|
*/
|
||||||
|
void secondaryMotd(@NonNull String secondary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how many players are currently online, the given number cannot be below 0.
|
||||||
|
*
|
||||||
|
* @param count the number to set
|
||||||
|
*/
|
||||||
|
void playerCount(int count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of players that can join this server, the given number cannot be below 1.
|
||||||
|
*
|
||||||
|
* @param max the number to set
|
||||||
|
*/
|
||||||
|
void maxPlayerCount(int max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the primary motd.
|
||||||
|
*
|
||||||
|
* @return the primary motd string
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String primaryMotd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the secondary motd.
|
||||||
|
*
|
||||||
|
* @return the secondary motd string
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String secondaryMotd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current number of players.
|
||||||
|
*
|
||||||
|
* @return number of players online
|
||||||
|
*/
|
||||||
|
@NonNegative
|
||||||
|
int playerCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of players that can join this server
|
||||||
|
*
|
||||||
|
* @return maximum number of players that can join
|
||||||
|
*/
|
||||||
|
int maxPlayerCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link InetSocketAddress} of the client pinging us.
|
||||||
|
*
|
||||||
|
* @return a {@link InetSocketAddress}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
InetSocketAddress address();
|
||||||
|
}
|
@ -50,9 +50,8 @@ public final class Constants {
|
|||||||
try {
|
try {
|
||||||
wsUri = new URI("wss://api.geysermc.org/ws");
|
wsUri = new URI("wss://api.geysermc.org/ws");
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
GeyserImpl.getInstance().getLogger().error("Unable to resolve api.geysermc.org! Check your internet connection.");
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
GLOBAL_API_WS_URI = wsUri;
|
GLOBAL_API_WS_URI = wsUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -540,6 +540,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
if (config.isNotifyOnNewBedrockUpdate()) {
|
if (config.isNotifyOnNewBedrockUpdate()) {
|
||||||
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
|
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VersionCheckUtils.checkForOutdatedJava(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.event.type;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.index.qual.NonNegative;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.BedrockPong;
|
||||||
|
import org.geysermc.geyser.api.event.connection.GeyserBedrockPingEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class GeyserBedrockPingEventImpl implements GeyserBedrockPingEvent {
|
||||||
|
private final InetSocketAddress address;
|
||||||
|
private final BedrockPong pong;
|
||||||
|
|
||||||
|
public GeyserBedrockPingEventImpl(BedrockPong pong, InetSocketAddress address) {
|
||||||
|
this.address = address;
|
||||||
|
this.pong = pong;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void primaryMotd(@Nonnull String primary) {
|
||||||
|
pong.motd(Objects.requireNonNull(primary, "Primary MOTD cannot be null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void secondaryMotd(@Nonnull String secondary) {
|
||||||
|
pong.subMotd(Objects.requireNonNull(secondary, "Secondary MOTD cannot be null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playerCount(int count) {
|
||||||
|
if (count < 0) throw new IllegalArgumentException("Player count cannot be below 0");
|
||||||
|
pong.playerCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maxPlayerCount(int max) {
|
||||||
|
if (max < 1) throw new IllegalArgumentException("Max player count cannot be below 1");
|
||||||
|
pong.maximumPlayerCount(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String primaryMotd() {
|
||||||
|
return pong.motd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String secondaryMotd() {
|
||||||
|
return pong.subMotd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNegative int playerCount() {
|
||||||
|
return pong.playerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxPlayerCount() {
|
||||||
|
return pong.maximumPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InetSocketAddress address() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
@ -82,15 +82,13 @@ public class GeyserExtensionClassLoader extends URLClassLoader {
|
|||||||
|
|
||||||
Class<?> result = this.classes.get(name);
|
Class<?> result = this.classes.get(name);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
if (checkGlobal) {
|
result = super.findClass(name);
|
||||||
|
if (result == null && checkGlobal) {
|
||||||
result = this.loader.classByName(name);
|
result = this.loader.classByName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) {
|
if (result != null) {
|
||||||
result = super.findClass(name);
|
this.loader.setClass(name, result);
|
||||||
if (result != null) {
|
|
||||||
this.loader.setClass(name, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.classes.put(name, result);
|
this.classes.put(name, result);
|
||||||
|
@ -197,7 +197,11 @@ public class Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!enchantments.isEmpty()) {
|
if (!enchantments.isEmpty()) {
|
||||||
tag.put(new ListTag("Enchantments", enchantments));
|
if ((this instanceof EnchantedBookItem)) {
|
||||||
|
tag.put(new ListTag("StoredEnchantments", enchantments));
|
||||||
|
} else {
|
||||||
|
tag.put(new ListTag("Enchantments", enchantments));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,9 @@ public final class GameProtocol {
|
|||||||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||||
* release of the game that Geyser supports.
|
* release of the game that Geyser supports.
|
||||||
*/
|
*/
|
||||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v582.CODEC;
|
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v582.CODEC.toBuilder()
|
||||||
|
.minecraftVersion("1.19.81")
|
||||||
|
.build();
|
||||||
/**
|
/**
|
||||||
* A list of all supported Bedrock versions that can join Geyser
|
* A list of all supported Bedrock versions that can join Geyser
|
||||||
*/
|
*/
|
||||||
@ -73,7 +75,9 @@ public final class GameProtocol {
|
|||||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v575.CODEC.toBuilder()
|
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v575.CODEC.toBuilder()
|
||||||
.minecraftVersion("1.19.70/1.19.71/1.19.73")
|
.minecraftVersion("1.19.70/1.19.71/1.19.73")
|
||||||
.build());
|
.build());
|
||||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||||
|
.minecraftVersion("1.19.80/1.19.81")
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,10 +33,10 @@ import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
|||||||
import org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer;
|
import org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent;
|
import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent;
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
public class GeyserServerInitializer extends BedrockServerInitializer {
|
public class GeyserServerInitializer extends BedrockServerInitializer {
|
||||||
private final GeyserImpl geyser;
|
private final GeyserImpl geyser;
|
||||||
@ -47,9 +47,21 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
|
|||||||
this.geyser = geyser;
|
this.geyser = geyser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postInitChannel(Channel channel) throws Exception {
|
||||||
|
super.postInitChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
|
public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
|
||||||
try {
|
try {
|
||||||
|
if (this.geyser.getGeyserServer().getProxiedAddresses() != null) {
|
||||||
|
InetSocketAddress address = this.geyser.getGeyserServer().getProxiedAddresses().get((InetSocketAddress) bedrockServerSession.getSocketAddress());
|
||||||
|
if (address != null) {
|
||||||
|
((GeyserBedrockPeer) bedrockServerSession.getPeer()).setProxiedAddress(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bedrockServerSession.setLogging(true);
|
bedrockServerSession.setLogging(true);
|
||||||
GeyserSession session = new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next());
|
GeyserSession session = new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next());
|
||||||
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, session));
|
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, session));
|
||||||
@ -65,21 +77,4 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
|
|||||||
protected BedrockPeer createPeer(Channel channel) {
|
protected BedrockPeer createPeer(Channel channel) {
|
||||||
return new GeyserBedrockPeer(channel, this::createSession);
|
return new GeyserBedrockPeer(channel, this::createSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
public void onUnhandledDatagram(@Nonnull ChannelHandlerContext ctx, @Nonnull DatagramPacket packet) {
|
|
||||||
try {
|
|
||||||
ByteBuf content = packet.content();
|
|
||||||
if (QueryPacketHandler.isQueryPacket(content)) {
|
|
||||||
new QueryPacketHandler(geyser, packet.sender(), content);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// Error must be caught or it will be swallowed
|
|
||||||
if (geyser.getConfig().isDebugMode()) {
|
|
||||||
geyser.getLogger().error("Error occurred during unhandled datagram!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
@ -29,8 +29,6 @@ import com.github.steveice10.packetlib.helper.TransportHelper;
|
|||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||||
@ -41,33 +39,32 @@ import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
|||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||||
import io.netty.handler.codec.haproxy.HAProxyCommand;
|
import lombok.Getter;
|
||||||
import io.netty.handler.codec.haproxy.HAProxyMessage;
|
import net.jodah.expiringmap.ExpirationPolicy;
|
||||||
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
|
import net.jodah.expiringmap.ExpiringMap;
|
||||||
import org.cloudburstmc.netty.channel.raknet.RakChannelFactory;
|
import org.cloudburstmc.netty.channel.raknet.RakChannelFactory;
|
||||||
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
|
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
|
||||||
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler;
|
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler;
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockPong;
|
import org.cloudburstmc.protocol.bedrock.BedrockPong;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
|
import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl;
|
||||||
import org.geysermc.geyser.network.CIDRMatcher;
|
import org.geysermc.geyser.network.CIDRMatcher;
|
||||||
import org.geysermc.geyser.network.GameProtocol;
|
import org.geysermc.geyser.network.GameProtocol;
|
||||||
import org.geysermc.geyser.network.GeyserBedrockPeer;
|
|
||||||
import org.geysermc.geyser.network.GeyserServerInitializer;
|
import org.geysermc.geyser.network.GeyserServerInitializer;
|
||||||
import org.geysermc.geyser.network.netty.handler.RakConnectionRequestHandler;
|
import org.geysermc.geyser.network.netty.handler.RakConnectionRequestHandler;
|
||||||
import org.geysermc.geyser.network.netty.handler.RakPingHandler;
|
import org.geysermc.geyser.network.netty.handler.RakPingHandler;
|
||||||
|
import org.geysermc.geyser.network.netty.proxy.ProxyServerHandler;
|
||||||
import org.geysermc.geyser.ping.GeyserPingInfo;
|
import org.geysermc.geyser.ping.GeyserPingInfo;
|
||||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
public final class GeyserServer {
|
public final class GeyserServer {
|
||||||
@ -89,6 +86,9 @@ public final class GeyserServer {
|
|||||||
private final EventLoopGroup group;
|
private final EventLoopGroup group;
|
||||||
private final ServerBootstrap bootstrap;
|
private final ServerBootstrap bootstrap;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
|
||||||
|
|
||||||
private ChannelFuture future;
|
private ChannelFuture future;
|
||||||
|
|
||||||
public GeyserServer(GeyserImpl geyser, int threadCount) {
|
public GeyserServer(GeyserImpl geyser, int threadCount) {
|
||||||
@ -96,6 +96,14 @@ public final class GeyserServer {
|
|||||||
this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
|
this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
|
||||||
|
|
||||||
this.bootstrap = this.createBootstrap(this.group);
|
this.bootstrap = this.createBootstrap(this.group);
|
||||||
|
|
||||||
|
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||||
|
this.proxiedAddresses = ExpiringMap.builder()
|
||||||
|
.expiration(30 + 1, TimeUnit.MINUTES)
|
||||||
|
.expirationPolicy(ExpirationPolicy.ACCESSED).build();
|
||||||
|
} else {
|
||||||
|
this.proxiedAddresses = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> bind(InetSocketAddress address) {
|
public CompletableFuture<Void> bind(InetSocketAddress address) {
|
||||||
@ -116,27 +124,7 @@ public final class GeyserServer {
|
|||||||
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
|
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
|
||||||
|
|
||||||
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||||
channel.pipeline().addFirst("proxy-protocol-decoder", new HAProxyMessageDecoder());
|
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
|
||||||
channel.pipeline().addAfter("proxy-protocol-decoder", "proxy-protocol-packet-handler", new ChannelInboundHandlerAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) throws Exception {
|
|
||||||
if (!(msg instanceof HAProxyMessage message)) {
|
|
||||||
super.channelRead(ctx, msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.command() == HAProxyCommand.PROXY) {
|
|
||||||
String address = message.sourceAddress();
|
|
||||||
int port = message.sourcePort();
|
|
||||||
|
|
||||||
SocketAddress realAddress = new InetSocketAddress(address, port);
|
|
||||||
|
|
||||||
GeyserBedrockPeer peer = (GeyserBedrockPeer) channel.pipeline().get(BedrockPeer.NAME);
|
|
||||||
peer.setProxiedAddress(realAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
@ -183,7 +171,16 @@ public final class GeyserServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
String ip;
|
||||||
|
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||||
|
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||||
|
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
|
||||||
|
} else {
|
||||||
|
ip = inetSocketAddress.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip = "<IP address withheld>";
|
||||||
|
}
|
||||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
|
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -208,7 +205,9 @@ public final class GeyserServer {
|
|||||||
.nintendoLimited(false)
|
.nintendoLimited(false)
|
||||||
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
|
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
|
||||||
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
||||||
.ipv4Port(this.geyser.getConfig().getBedrock().port());
|
.ipv4Port(this.geyser.getConfig().getBedrock().port())
|
||||||
|
.ipv6Port(this.geyser.getConfig().getBedrock().port())
|
||||||
|
.serverId(future.channel().config().getOption(RakChannelOption.RAK_GUID));
|
||||||
|
|
||||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||||
@ -222,6 +221,17 @@ public final class GeyserServer {
|
|||||||
pong.subMotd(config.getBedrock().secondaryMotd());
|
pong.subMotd(config.getBedrock().secondaryMotd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Placed here to prevent overriding values set in the ping event.
|
||||||
|
if (config.isPassthroughPlayerCounts() && pingInfo != null) {
|
||||||
|
pong.playerCount(pingInfo.getPlayers().getOnline());
|
||||||
|
pong.maximumPlayerCount(pingInfo.getPlayers().getMax());
|
||||||
|
} else {
|
||||||
|
pong.playerCount(geyser.getSessionManager().getSessions().size());
|
||||||
|
pong.maximumPlayerCount(config.getMaxPlayers());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.geyser.eventBus().fire(new GeyserBedrockPingEventImpl(pong, inetSocketAddress));
|
||||||
|
|
||||||
// https://github.com/GeyserMC/Geyser/issues/3388
|
// https://github.com/GeyserMC/Geyser/issues/3388
|
||||||
pong.motd(pong.motd().replace(';', ':'));
|
pong.motd(pong.motd().replace(';', ':'));
|
||||||
pong.subMotd(pong.subMotd().replace(';', ':'));
|
pong.subMotd(pong.subMotd().replace(';', ':'));
|
||||||
@ -253,14 +263,6 @@ public final class GeyserServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isPassthroughPlayerCounts() && pingInfo != null) {
|
|
||||||
pong.playerCount(pingInfo.getPlayers().getOnline());
|
|
||||||
pong.maximumPlayerCount(pingInfo.getPlayers().getMax());
|
|
||||||
} else {
|
|
||||||
pong.playerCount(geyser.getSessionManager().getSessions().size());
|
|
||||||
pong.maximumPlayerCount(config.getMaxPlayers());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Bedrock will not even attempt a connection if the client thinks the server is full
|
//Bedrock will not even attempt a connection if the client thinks the server is full
|
||||||
//so we have to fake it not being full
|
//so we have to fake it not being full
|
||||||
if (pong.playerCount() >= pong.maximumPlayerCount()) {
|
if (pong.playerCount() >= pong.maximumPlayerCount()) {
|
||||||
|
@ -0,0 +1,604 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.network.netty.proxy;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.codec.ProtocolDetectionResult;
|
||||||
|
import io.netty.handler.codec.haproxy.*;
|
||||||
|
import io.netty.util.ByteProcessor;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an HAProxy proxy protocol header
|
||||||
|
*
|
||||||
|
* @see <a href="https://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
|
||||||
|
* @see <a href="https://github.com/netty/netty/blob/4.1/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoder.java">Netty implementation</a>
|
||||||
|
*/
|
||||||
|
public final class ProxyProtocolDecoder {
|
||||||
|
/**
|
||||||
|
* {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V1}.
|
||||||
|
*/
|
||||||
|
private static final ProtocolDetectionResult<HAProxyProtocolVersion> DETECTION_RESULT_V1 =
|
||||||
|
ProtocolDetectionResult.detected(HAProxyProtocolVersion.V1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V2}.
|
||||||
|
*/
|
||||||
|
private static final ProtocolDetectionResult<HAProxyProtocolVersion> DETECTION_RESULT_V2 =
|
||||||
|
ProtocolDetectionResult.detected(HAProxyProtocolVersion.V2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to extract a header frame out of the {@link ByteBuf} and return it.
|
||||||
|
*/
|
||||||
|
private HeaderExtractor headerExtractor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code true} if we're discarding input because we're already over maxLength
|
||||||
|
*/
|
||||||
|
private boolean discarding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of discarded bytes
|
||||||
|
*/
|
||||||
|
private int discardedBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code true} if we're finished decoding the proxy protocol header
|
||||||
|
*/
|
||||||
|
private boolean finished;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol specification version
|
||||||
|
*/
|
||||||
|
private int decodingVersion = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest v2 spec (2014/05/18) allows for additional data to be sent in the proxy protocol header beyond the
|
||||||
|
* address information block so now we need a configurable max header size
|
||||||
|
*/
|
||||||
|
private final int v2MaxHeaderSize = 16 + 65535; // TODO: need to calculate max length if TLVs are desired.
|
||||||
|
|
||||||
|
private ProxyProtocolDecoder(int version) {
|
||||||
|
this.decodingVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HAProxyMessage decode(ByteBuf packet, int version) {
|
||||||
|
if (version == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ProxyProtocolDecoder decoder = new ProxyProtocolDecoder(version);
|
||||||
|
return decoder.decodeHeader(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HAProxyMessage decodeHeader(ByteBuf in) {
|
||||||
|
final ByteBuf decoded = decodingVersion == 1 ? decodeLine(in) : decodeStruct(in);
|
||||||
|
if (decoded == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
try {
|
||||||
|
if (decodingVersion == 1) {
|
||||||
|
return decodeHeader(decoded.toString(CharsetUtil.US_ASCII));
|
||||||
|
} else {
|
||||||
|
return decodeHeader0(decoded);
|
||||||
|
}
|
||||||
|
} catch (HAProxyProtocolException e) {
|
||||||
|
throw fail(null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a version 2, binary proxy protocol header. Copied from HAProxyMessage.
|
||||||
|
*
|
||||||
|
* @param header a version 2 proxy protocol header
|
||||||
|
* @return {@link HAProxyMessage} instance
|
||||||
|
* @throws HAProxyProtocolException if any portion of the header is invalid
|
||||||
|
*/
|
||||||
|
static HAProxyMessage decodeHeader0(ByteBuf header) {
|
||||||
|
Objects.requireNonNull(header, "header");
|
||||||
|
|
||||||
|
if (header.readableBytes() < 16) {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"incomplete header: " + header.readableBytes() + " bytes (expected: 16+ bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per spec, the 13th byte is the protocol version and command byte
|
||||||
|
header.skipBytes(12);
|
||||||
|
final byte verCmdByte = header.readByte();
|
||||||
|
|
||||||
|
HAProxyProtocolVersion ver;
|
||||||
|
try {
|
||||||
|
ver = HAProxyProtocolVersion.valueOf(verCmdByte);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new HAProxyProtocolException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ver != HAProxyProtocolVersion.V2) {
|
||||||
|
throw new HAProxyProtocolException("version 1 unsupported: 0x" + Integer.toHexString(verCmdByte));
|
||||||
|
}
|
||||||
|
|
||||||
|
HAProxyCommand cmd;
|
||||||
|
try {
|
||||||
|
cmd = HAProxyCommand.valueOf(verCmdByte);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new HAProxyProtocolException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == HAProxyCommand.LOCAL) {
|
||||||
|
return unknownMsg(HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per spec, the 14th byte is the protocol and address family byte
|
||||||
|
HAProxyProxiedProtocol protAndFam;
|
||||||
|
try {
|
||||||
|
protAndFam = HAProxyProxiedProtocol.valueOf(header.readByte());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new HAProxyProtocolException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
|
||||||
|
return unknownMsg(HAProxyProtocolVersion.V2, HAProxyCommand.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
int addressInfoLen = header.readUnsignedShort();
|
||||||
|
|
||||||
|
String srcAddress;
|
||||||
|
String dstAddress;
|
||||||
|
int addressLen;
|
||||||
|
int srcPort = 0;
|
||||||
|
int dstPort = 0;
|
||||||
|
|
||||||
|
HAProxyProxiedProtocol.AddressFamily addressFamily = protAndFam.addressFamily();
|
||||||
|
|
||||||
|
if (addressFamily == HAProxyProxiedProtocol.AddressFamily.AF_UNIX) {
|
||||||
|
// unix sockets require 216 bytes for address information
|
||||||
|
if (addressInfoLen < 216 || header.readableBytes() < 216) {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"incomplete UNIX socket address information: " +
|
||||||
|
Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 216+ bytes)");
|
||||||
|
}
|
||||||
|
int startIdx = header.readerIndex();
|
||||||
|
int addressEnd = header.forEachByte(startIdx, 108, ByteProcessor.FIND_NUL);
|
||||||
|
if (addressEnd == -1) {
|
||||||
|
addressLen = 108;
|
||||||
|
} else {
|
||||||
|
addressLen = addressEnd - startIdx;
|
||||||
|
}
|
||||||
|
srcAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
|
||||||
|
|
||||||
|
startIdx += 108;
|
||||||
|
|
||||||
|
addressEnd = header.forEachByte(startIdx, 108, ByteProcessor.FIND_NUL);
|
||||||
|
if (addressEnd == -1) {
|
||||||
|
addressLen = 108;
|
||||||
|
} else {
|
||||||
|
addressLen = addressEnd - startIdx;
|
||||||
|
}
|
||||||
|
dstAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
|
||||||
|
// AF_UNIX defines that exactly 108 bytes are reserved for the address. The previous methods
|
||||||
|
// did not increase the reader index although we already consumed the information.
|
||||||
|
header.readerIndex(startIdx + 108);
|
||||||
|
} else {
|
||||||
|
if (addressFamily == HAProxyProxiedProtocol.AddressFamily.AF_IPv4) {
|
||||||
|
// IPv4 requires 12 bytes for address information
|
||||||
|
if (addressInfoLen < 12 || header.readableBytes() < 12) {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"incomplete IPv4 address information: " +
|
||||||
|
Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 12+ bytes)");
|
||||||
|
}
|
||||||
|
addressLen = 4;
|
||||||
|
} else if (addressFamily == HAProxyProxiedProtocol.AddressFamily.AF_IPv6) {
|
||||||
|
// IPv6 requires 36 bytes for address information
|
||||||
|
if (addressInfoLen < 36 || header.readableBytes() < 36) {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"incomplete IPv6 address information: " +
|
||||||
|
Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 36+ bytes)");
|
||||||
|
}
|
||||||
|
addressLen = 16;
|
||||||
|
} else {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"unable to parse address information (unknown address family: " + addressFamily + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per spec, the src address begins at the 17th byte
|
||||||
|
srcAddress = ipBytesToString(header, addressLen);
|
||||||
|
dstAddress = ipBytesToString(header, addressLen);
|
||||||
|
srcPort = header.readUnsignedShort();
|
||||||
|
dstPort = header.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (skipNextTLV(header)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert ip address bytes to string representation. From IPBytesToString
|
||||||
|
*
|
||||||
|
* @param header buffer containing ip address bytes
|
||||||
|
* @param addressLen number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
|
||||||
|
* @return string representation of the ip address
|
||||||
|
*/
|
||||||
|
private static String ipBytesToString(ByteBuf header, int addressLen) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
final int ipv4Len = 4;
|
||||||
|
final int ipv6Len = 8;
|
||||||
|
if (addressLen == ipv4Len) {
|
||||||
|
for (int i = 0; i < ipv4Len; i++) {
|
||||||
|
sb.append(header.readByte() & 0xff);
|
||||||
|
sb.append('.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < ipv6Len; i++) {
|
||||||
|
sb.append(Integer.toHexString(header.readUnsignedShort()));
|
||||||
|
sb.append(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.setLength(sb.length() - 1);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From HAProxyMessage
|
||||||
|
*/
|
||||||
|
private static boolean skipNextTLV(final ByteBuf header) {
|
||||||
|
// We need at least 4 bytes for a TLV
|
||||||
|
if (header.readableBytes() < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.skipBytes(1);
|
||||||
|
header.skipBytes(header.readUnsignedShort());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HAProxyMessage decodeHeader(String header) {
|
||||||
|
if (header == null) {
|
||||||
|
throw new HAProxyProtocolException("header");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = header.split(" ");
|
||||||
|
int numParts = parts.length;
|
||||||
|
|
||||||
|
if (numParts < 2) {
|
||||||
|
throw new HAProxyProtocolException(
|
||||||
|
"invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"PROXY".equals(parts[0])) {
|
||||||
|
throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
HAProxyProxiedProtocol protAndFam;
|
||||||
|
try {
|
||||||
|
protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new HAProxyProtocolException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
|
||||||
|
protAndFam != HAProxyProxiedProtocol.TCP6 &&
|
||||||
|
protAndFam != HAProxyProxiedProtocol.UNKNOWN) {
|
||||||
|
throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
|
||||||
|
return unknownMsg(HAProxyProtocolVersion.V1, HAProxyCommand.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numParts != 6) {
|
||||||
|
throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new HAProxyMessage(
|
||||||
|
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
|
||||||
|
protAndFam, parts[2], parts[3], portStringToInt(parts[4]), portStringToInt(parts[5]));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new HAProxyProtocolException("invalid HAProxy message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int portStringToInt(String value) {
|
||||||
|
int port;
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("invalid port: " + value, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port <= 0 || port > 65535) {
|
||||||
|
throw new IllegalArgumentException("invalid port: " + value + " (expected: 1 ~ 65535)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
|
||||||
|
* 'UNKNOWN' we must discard all other header values.
|
||||||
|
*/
|
||||||
|
private static HAProxyMessage unknownMsg(HAProxyProtocolVersion version, HAProxyCommand command) {
|
||||||
|
return new HAProxyMessage(version, command, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final byte[] BINARY_PREFIX = {
|
||||||
|
(byte) 0x0D,
|
||||||
|
(byte) 0x0A,
|
||||||
|
(byte) 0x0D,
|
||||||
|
(byte) 0x0A,
|
||||||
|
(byte) 0x00,
|
||||||
|
(byte) 0x0D,
|
||||||
|
(byte) 0x0A,
|
||||||
|
(byte) 0x51,
|
||||||
|
(byte) 0x55,
|
||||||
|
(byte) 0x49,
|
||||||
|
(byte) 0x54,
|
||||||
|
(byte) 0x0A
|
||||||
|
};
|
||||||
|
static final int BINARY_PREFIX_LENGTH = BINARY_PREFIX.length;
|
||||||
|
|
||||||
|
public static int findVersion(final ByteBuf buffer) {
|
||||||
|
final int n = buffer.readableBytes();
|
||||||
|
// per spec, the version number is found in the 13th byte
|
||||||
|
if (n < 13) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = buffer.readerIndex();
|
||||||
|
return match(BINARY_PREFIX, buffer, idx) ? buffer.getByte(idx + BINARY_PREFIX_LENGTH) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a frame out of the {@link ByteBuf} and return it.
|
||||||
|
*
|
||||||
|
* @param buffer the {@link ByteBuf} from which to read data
|
||||||
|
* @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
|
||||||
|
* be created
|
||||||
|
*/
|
||||||
|
private ByteBuf decodeStruct(ByteBuf buffer) {
|
||||||
|
if (headerExtractor == null) {
|
||||||
|
headerExtractor = new StructHeaderExtractor(v2MaxHeaderSize);
|
||||||
|
}
|
||||||
|
return headerExtractor.extract(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a frame out of the {@link ByteBuf} and return it.
|
||||||
|
*
|
||||||
|
* @param buffer the {@link ByteBuf} from which to read data
|
||||||
|
* @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
|
||||||
|
* be created
|
||||||
|
*/
|
||||||
|
private ByteBuf decodeLine(ByteBuf buffer) {
|
||||||
|
if (headerExtractor == null) {
|
||||||
|
headerExtractor = new LineHeaderExtractor(108);
|
||||||
|
}
|
||||||
|
return headerExtractor.extract(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failOverLimit(String length) {
|
||||||
|
int maxLength = decodingVersion == 1 ? 108 : v2MaxHeaderSize;
|
||||||
|
throw fail("header length (" + length + ") exceeds the allowed maximum (" + maxLength + ')', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HAProxyProtocolException fail(String errMsg, Exception e) {
|
||||||
|
finished = true;
|
||||||
|
HAProxyProtocolException ppex;
|
||||||
|
if (errMsg != null && e != null) {
|
||||||
|
ppex = new HAProxyProtocolException(errMsg, e);
|
||||||
|
} else if (errMsg != null) {
|
||||||
|
ppex = new HAProxyProtocolException(errMsg);
|
||||||
|
} else if (e != null) {
|
||||||
|
ppex = new HAProxyProtocolException(e);
|
||||||
|
} else {
|
||||||
|
ppex = new HAProxyProtocolException();
|
||||||
|
}
|
||||||
|
return ppex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final byte[] TEXT_PREFIX = {
|
||||||
|
(byte) 'P',
|
||||||
|
(byte) 'R',
|
||||||
|
(byte) 'O',
|
||||||
|
(byte) 'X',
|
||||||
|
(byte) 'Y',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link ProtocolDetectionResult} for the given {@link ByteBuf}.
|
||||||
|
*/
|
||||||
|
public static ProtocolDetectionResult<HAProxyProtocolVersion> detectProtocol(ByteBuf buffer) {
|
||||||
|
if (buffer.readableBytes() < 12) {
|
||||||
|
return ProtocolDetectionResult.needsMoreData();
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = buffer.readerIndex();
|
||||||
|
|
||||||
|
if (match(BINARY_PREFIX, buffer, idx)) {
|
||||||
|
return DETECTION_RESULT_V2;
|
||||||
|
}
|
||||||
|
if (match(TEXT_PREFIX, buffer, idx)) {
|
||||||
|
return DETECTION_RESULT_V1;
|
||||||
|
}
|
||||||
|
return ProtocolDetectionResult.invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean match(byte[] prefix, ByteBuf buffer, int idx) {
|
||||||
|
for (int i = 0; i < prefix.length; i++) {
|
||||||
|
final byte b = buffer.getByte(idx + i);
|
||||||
|
if (b != prefix[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HeaderExtractor create a header frame out of the {@link ByteBuf}.
|
||||||
|
*/
|
||||||
|
private abstract class HeaderExtractor {
|
||||||
|
/** Header max size */
|
||||||
|
private final int maxHeaderSize;
|
||||||
|
|
||||||
|
protected HeaderExtractor(int maxHeaderSize) {
|
||||||
|
this.maxHeaderSize = maxHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a frame out of the {@link ByteBuf} and return it.
|
||||||
|
*
|
||||||
|
* @param buffer the {@link ByteBuf} from which to read data
|
||||||
|
* @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
|
||||||
|
* be created
|
||||||
|
*/
|
||||||
|
public ByteBuf extract(ByteBuf buffer) {
|
||||||
|
final int eoh = findEndOfHeader(buffer);
|
||||||
|
if (!discarding) {
|
||||||
|
if (eoh >= 0) {
|
||||||
|
final int length = eoh - buffer.readerIndex();
|
||||||
|
if (length > maxHeaderSize) {
|
||||||
|
buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
|
||||||
|
failOverLimit(String.valueOf(length));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ByteBuf frame = buffer.readSlice(length);
|
||||||
|
buffer.skipBytes(delimiterLength(buffer, eoh));
|
||||||
|
return frame;
|
||||||
|
} else {
|
||||||
|
final int length = buffer.readableBytes();
|
||||||
|
if (length > maxHeaderSize) {
|
||||||
|
discardedBytes = length;
|
||||||
|
buffer.skipBytes(length);
|
||||||
|
discarding = true;
|
||||||
|
failOverLimit("over " + discardedBytes);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (eoh >= 0) {
|
||||||
|
final int length = discardedBytes + eoh - buffer.readerIndex();
|
||||||
|
buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
|
||||||
|
discardedBytes = 0;
|
||||||
|
discarding = false;
|
||||||
|
failOverLimit("over " + length);
|
||||||
|
} else {
|
||||||
|
discardedBytes += buffer.readableBytes();
|
||||||
|
buffer.skipBytes(buffer.readableBytes());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the end of the header from the given {@link ByteBuf},the end may be a CRLF, or the length given by the
|
||||||
|
* header.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to be searched
|
||||||
|
* @return {@code -1} if can not find the end, otherwise return the buffer index of end
|
||||||
|
*/
|
||||||
|
protected abstract int findEndOfHeader(ByteBuf buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the length of the header delimiter.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer where delimiter is located
|
||||||
|
* @param eoh index of delimiter
|
||||||
|
* @return length of the delimiter
|
||||||
|
*/
|
||||||
|
protected abstract int delimiterLength(ByteBuf buffer, int eoh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class LineHeaderExtractor extends HeaderExtractor {
|
||||||
|
|
||||||
|
LineHeaderExtractor(int maxHeaderSize) {
|
||||||
|
super(maxHeaderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index in the buffer of the end of line found.
|
||||||
|
* Returns -1 if no end of line was found in the buffer.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected int findEndOfHeader(ByteBuf buffer) {
|
||||||
|
final int n = buffer.writerIndex();
|
||||||
|
for (int i = buffer.readerIndex(); i < n; i++) {
|
||||||
|
final byte b = buffer.getByte(i);
|
||||||
|
if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
|
||||||
|
return i; // \r\n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // Not found.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int delimiterLength(ByteBuf buffer, int eoh) {
|
||||||
|
return buffer.getByte(eoh) == '\r' ? 2 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StructHeaderExtractor extends HeaderExtractor {
|
||||||
|
|
||||||
|
StructHeaderExtractor(int maxHeaderSize) {
|
||||||
|
super(maxHeaderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index in the buffer of the end of header if found.
|
||||||
|
* Returns -1 if no end of header was found in the buffer.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected int findEndOfHeader(ByteBuf buffer) {
|
||||||
|
final int n = buffer.readableBytes();
|
||||||
|
|
||||||
|
// per spec, the 15th and 16th bytes contain the address length in bytes
|
||||||
|
if (n < 16) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = buffer.readerIndex() + 14;
|
||||||
|
|
||||||
|
// the total header length will be a fixed 16 byte sequence + the dynamic address information block
|
||||||
|
int totalHeaderBytes = 16 + buffer.getUnsignedShort(offset);
|
||||||
|
|
||||||
|
// ensure we actually have the full header available
|
||||||
|
if (n >= totalHeaderBytes) {
|
||||||
|
return totalHeaderBytes;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int delimiterLength(ByteBuf buffer, int eoh) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.network.netty.proxy;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyMessage;
|
||||||
|
import io.netty.handler.codec.haproxy.HAProxyProtocolException;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.network.GeyserBedrockPeer;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
@ChannelHandler.Sharable
|
||||||
|
public class ProxyServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||||
|
private static final InternalLogger log = InternalLoggerFactory.getInstance(ProxyServerHandler.class);
|
||||||
|
public static final String NAME = "rak-proxy-server-handler";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
|
||||||
|
ByteBuf content = packet.content();
|
||||||
|
GeyserBedrockPeer peer = (GeyserBedrockPeer) ctx.pipeline().get(BedrockPeer.NAME);
|
||||||
|
int detectedVersion = peer != null ? -1 : ProxyProtocolDecoder.findVersion(content);
|
||||||
|
InetSocketAddress presentAddress = GeyserImpl.getInstance().getGeyserServer().getProxiedAddresses().get(packet.sender());
|
||||||
|
|
||||||
|
if (presentAddress == null && detectedVersion == -1) {
|
||||||
|
// We haven't received a header from given address before and we couldn't detect a
|
||||||
|
// PROXY header, ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presentAddress == null) {
|
||||||
|
final HAProxyMessage decoded;
|
||||||
|
try {
|
||||||
|
if ((decoded = ProxyProtocolDecoder.decode(content, detectedVersion)) == null) {
|
||||||
|
// PROXY header was not present in the packet, ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (HAProxyProtocolException e) {
|
||||||
|
log.debug("{} sent malformed PROXY header", packet.sender(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
presentAddress = new InetSocketAddress(decoded.sourceAddress(), decoded.sourcePort());
|
||||||
|
log.debug("Got PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||||
|
GeyserImpl.getInstance().getGeyserServer().getProxiedAddresses().put(packet.sender(), presentAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||||
|
ctx.fireChannelRead(packet.retain());
|
||||||
|
}
|
||||||
|
}
|
@ -58,20 +58,20 @@ public class SoundEventsRegistryLoader extends EffectRegistryLoader<Map<LevelEve
|
|||||||
LevelEventTranslator transformer = null;
|
LevelEventTranslator transformer = null;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "soundLevel" -> {
|
case "soundLevel" -> {
|
||||||
javaEffect = LevelEvent.valueOf(entry.getKey());
|
javaEffect = com.github.steveice10.mc.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||||
LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").asText());
|
LevelEventType levelEventType = org.cloudburstmc.protocol.bedrock.data.LevelEvent.valueOf(node.get("name").asText());
|
||||||
int data = node.has("data") ? node.get("data").intValue() : 0;
|
int data = node.has("data") ? node.get("data").intValue() : 0;
|
||||||
transformer = new SoundLevelEventTranslator(levelEventType, data);
|
transformer = new SoundLevelEventTranslator(levelEventType, data);
|
||||||
}
|
}
|
||||||
case "soundEvent" -> {
|
case "soundEvent" -> {
|
||||||
javaEffect = LevelEvent.valueOf(entry.getKey());
|
javaEffect = com.github.steveice10.mc.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||||
org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").asText());
|
org.cloudburstmc.protocol.bedrock.data.SoundEvent soundEvent = org.cloudburstmc.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").asText());
|
||||||
String identifier = node.has("identifier") ? node.get("identifier").asText() : "";
|
String identifier = node.has("identifier") ? node.get("identifier").asText() : "";
|
||||||
int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1;
|
int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1;
|
||||||
transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData);
|
transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData);
|
||||||
}
|
}
|
||||||
case "playSound" -> {
|
case "playSound" -> {
|
||||||
javaEffect = LevelEvent.valueOf(entry.getKey());
|
javaEffect = com.github.steveice10.mc.protocol.data.game.level.event.LevelEventType.valueOf(entry.getKey());
|
||||||
String name = node.get("name").asText();
|
String name = node.get("name").asText();
|
||||||
float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f;
|
float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f;
|
||||||
boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue();
|
boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue();
|
||||||
|
@ -103,10 +103,10 @@ public class CustomItemRegistryPopulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GeyserCustomMappingData registerCustomItem(String customItemName, GeyserMappingItem javaItem, CustomItemData customItemData, int bedrockId) {
|
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId) {
|
||||||
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
||||||
|
|
||||||
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, customItemName, bedrockId);
|
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId);
|
||||||
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
|
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
|
||||||
|
|
||||||
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
|
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
|
||||||
@ -164,7 +164,7 @@ public class CustomItemRegistryPopulator {
|
|||||||
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
|
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, GeyserMappingItem mapping,
|
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
|
||||||
String customItemName, int customItemId) {
|
String customItemName, int customItemId) {
|
||||||
NbtMapBuilder builder = NbtMap.builder();
|
NbtMapBuilder builder = NbtMap.builder();
|
||||||
builder.putString("name", customItemName)
|
builder.putString("name", customItemName)
|
||||||
@ -173,7 +173,7 @@ public class CustomItemRegistryPopulator {
|
|||||||
NbtMapBuilder itemProperties = NbtMap.builder();
|
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||||
|
|
||||||
setupBasicItemInfo(mapping.getMaxDamage(), mapping.getStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder);
|
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder);
|
||||||
|
|
||||||
boolean canDestroyInCreative = true;
|
boolean canDestroyInCreative = true;
|
||||||
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
||||||
|
@ -88,9 +88,15 @@ public class ItemRegistryPopulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void populate() {
|
public static void populate() {
|
||||||
|
Map<Item, String> manualFallback = new HashMap<>();
|
||||||
|
manualFallback.put(Items.ENDER_DRAGON_SPAWN_EGG, "minecraft:enderman_spawn_egg");
|
||||||
|
manualFallback.put(Items.WITHER_SPAWN_EGG, "minecraft:wither_skeleton_spawn_egg");
|
||||||
|
manualFallback.put(Items.SNOW_GOLEM_SPAWN_EGG, "minecraft:polar_bear_spawn_egg");
|
||||||
|
manualFallback.put(Items.IRON_GOLEM_SPAWN_EGG, "minecraft:villager_spawn_egg");
|
||||||
|
|
||||||
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
||||||
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), manualFallback));
|
||||||
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), manualFallback));
|
||||||
paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||||
paletteVersions.put("1_19_70", new PaletteVersion(Bedrock_v575.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_70", new PaletteVersion(Bedrock_v575.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||||
paletteVersions.put("1_19_80", new PaletteVersion(Bedrock_v582.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_80", new PaletteVersion(Bedrock_v582.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||||
@ -190,6 +196,11 @@ public class ItemRegistryPopulator {
|
|||||||
Set<Item> javaOnlyItems = new ObjectOpenHashSet<>();
|
Set<Item> javaOnlyItems = new ObjectOpenHashSet<>();
|
||||||
Collections.addAll(javaOnlyItems, Items.SPECTRAL_ARROW, Items.DEBUG_STICK,
|
Collections.addAll(javaOnlyItems, Items.SPECTRAL_ARROW, Items.DEBUG_STICK,
|
||||||
Items.KNOWLEDGE_BOOK, Items.TIPPED_ARROW, Items.BUNDLE);
|
Items.KNOWLEDGE_BOOK, Items.TIPPED_ARROW, Items.BUNDLE);
|
||||||
|
// these spawn eggs exist in 1.19.60+;
|
||||||
|
if (palette.getValue().protocolVersion() < Bedrock_v567.CODEC.getProtocolVersion()) {
|
||||||
|
Collections.addAll(javaOnlyItems, Items.IRON_GOLEM_SPAWN_EGG, Items.SNOW_GOLEM_SPAWN_EGG,
|
||||||
|
Items.WITHER_SPAWN_EGG, Items.ENDER_DRAGON_SPAWN_EGG);
|
||||||
|
}
|
||||||
javaOnlyItems.add(Items.DECORATED_POT);
|
javaOnlyItems.add(Items.DECORATED_POT);
|
||||||
if (!customItemsAllowed) {
|
if (!customItemsAllowed) {
|
||||||
javaOnlyItems.add(Items.FURNACE_MINECART);
|
javaOnlyItems.add(Items.FURNACE_MINECART);
|
||||||
@ -404,7 +415,7 @@ public class ItemRegistryPopulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(
|
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(
|
||||||
customItemName, mappingItem, customItem, customProtocolId
|
customItemName, javaItem, mappingItem, customItem, customProtocolId
|
||||||
);
|
);
|
||||||
// ComponentItemData - used to register some custom properties
|
// ComponentItemData - used to register some custom properties
|
||||||
componentItemData.add(customMapping.componentItemData());
|
componentItemData.add(customMapping.componentItemData());
|
||||||
@ -474,7 +485,7 @@ public class ItemRegistryPopulator {
|
|||||||
.build());
|
.build());
|
||||||
|
|
||||||
creativeItems.add(ItemData.builder()
|
creativeItems.add(ItemData.builder()
|
||||||
.netId(creativeNetId.getAndIncrement())
|
.netId(creativeNetId.incrementAndGet())
|
||||||
.definition(definition)
|
.definition(definition)
|
||||||
.count(1)
|
.count(1)
|
||||||
.build());
|
.build());
|
||||||
@ -502,11 +513,12 @@ public class ItemRegistryPopulator {
|
|||||||
mappings.add(ItemMapping.AIR);
|
mappings.add(ItemMapping.AIR);
|
||||||
}
|
}
|
||||||
mappings.set(javaItem.javaId(), mapping);
|
mappings.set(javaItem.javaId(), mapping);
|
||||||
|
registry.put(customItemId, mapping.getBedrockDefinition());
|
||||||
|
|
||||||
if (customItem.creativeGroup() != null || customItem.creativeCategory().isPresent()) {
|
if (customItem.creativeGroup() != null || customItem.creativeCategory().isPresent()) {
|
||||||
creativeItems.add(ItemData.builder()
|
creativeItems.add(ItemData.builder()
|
||||||
.definition(registration.mapping().getBedrockDefinition())
|
.definition(registration.mapping().getBedrockDefinition())
|
||||||
.netId(creativeNetId.getAndIncrement())
|
.netId(creativeNetId.incrementAndGet())
|
||||||
.count(1)
|
.count(1)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,10 @@ public class GeyserMappingItem {
|
|||||||
@JsonProperty("bedrock_data") int bedrockData;
|
@JsonProperty("bedrock_data") int bedrockData;
|
||||||
Integer firstBlockRuntimeId;
|
Integer firstBlockRuntimeId;
|
||||||
Integer lastBlockRuntimeId;
|
Integer lastBlockRuntimeId;
|
||||||
@JsonProperty("stack_size") int stackSize = 64;
|
|
||||||
@JsonProperty("tool_type") String toolType;
|
@JsonProperty("tool_type") String toolType;
|
||||||
@JsonProperty("tool_tier") String toolTier;
|
@JsonProperty("tool_tier") String toolTier;
|
||||||
@JsonProperty("armor_type") String armorType;
|
@JsonProperty("armor_type") String armorType;
|
||||||
@JsonProperty("protection_value") int protectionValue;
|
@JsonProperty("protection_value") int protectionValue;
|
||||||
@JsonProperty("max_damage") int maxDamage = 0;
|
|
||||||
@JsonProperty("is_edible") boolean edible = false;
|
@JsonProperty("is_edible") boolean edible = false;
|
||||||
@JsonProperty("is_entity_placer") boolean entityPlacer = false;
|
@JsonProperty("is_entity_placer") boolean entityPlacer = false;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ import org.java_websocket.handshake.ServerHandshake;
|
|||||||
|
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -175,6 +176,10 @@ public final class FloodgateSkinUploader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception ex) {
|
public void onError(Exception ex) {
|
||||||
|
if (ex instanceof UnknownHostException) {
|
||||||
|
logger.error("Unable to resolve the skin api! This can be caused by your connection or the skin api being unreachable. " + ex.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ex instanceof ConnectException || ex instanceof SSLException) {
|
if (ex instanceof ConnectException || ex instanceof SSLException) {
|
||||||
if (logger.isDebug()) {
|
if (logger.isDebug()) {
|
||||||
logger.error("[debug] Got an error", ex);
|
logger.error("[debug] Got an error", ex);
|
||||||
@ -237,4 +242,4 @@ public final class FloodgateSkinUploader {
|
|||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,6 +34,7 @@ import org.geysermc.geyser.network.GameProtocol;
|
|||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.geysermc.geyser.util.SignUtils;
|
import org.geysermc.geyser.util.SignUtils;
|
||||||
|
|
||||||
@Translator(packet = BlockEntityDataPacket.class)
|
@Translator(packet = BlockEntityDataPacket.class)
|
||||||
@ -52,6 +53,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
|||||||
} else {
|
} else {
|
||||||
text = tag.getString("Text");
|
text = tag.getString("Text");
|
||||||
}
|
}
|
||||||
|
text = MessageTranslator.convertToPlainText(text);
|
||||||
// Note: as of 1.18.30, only one packet is sent from Bedrock when the sign is finished.
|
// Note: as of 1.18.30, only one packet is sent from Bedrock when the sign is finished.
|
||||||
// Previous versions did not have this behavior.
|
// Previous versions did not have this behavior.
|
||||||
StringBuilder newMessage = new StringBuilder();
|
StringBuilder newMessage = new StringBuilder();
|
||||||
|
@ -36,6 +36,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
|
|||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -68,19 +69,19 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
|||||||
for (int i = pages.size(); i < page; i++) {
|
for (int i = pages.size(); i < page; i++) {
|
||||||
pages.add(i, new StringTag("", ""));
|
pages.add(i, new StringTag("", ""));
|
||||||
}
|
}
|
||||||
pages.add(page, new StringTag("", packet.getText()));
|
pages.add(page, new StringTag("", MessageTranslator.convertToPlainText(packet.getText())));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Called whenever a page is modified
|
// Called whenever a page is modified
|
||||||
case REPLACE_PAGE: {
|
case REPLACE_PAGE: {
|
||||||
if (page < pages.size()) {
|
if (page < pages.size()) {
|
||||||
pages.set(page, new StringTag("", packet.getText()));
|
pages.set(page, new StringTag("", MessageTranslator.convertToPlainText(packet.getText())));
|
||||||
} else {
|
} else {
|
||||||
// Add empty pages in between
|
// Add empty pages in between
|
||||||
for (int i = pages.size(); i < page; i++) {
|
for (int i = pages.size(); i < page; i++) {
|
||||||
pages.add(i, new StringTag("", ""));
|
pages.add(i, new StringTag("", ""));
|
||||||
}
|
}
|
||||||
pages.add(page, new StringTag("", packet.getText()));
|
pages.add(page, new StringTag("", MessageTranslator.convertToPlainText(packet.getText())));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -98,8 +99,8 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SIGN_BOOK: {
|
case SIGN_BOOK: {
|
||||||
tag.put(new StringTag("author", packet.getAuthor()));
|
tag.put(new StringTag("author", MessageTranslator.convertToPlainText(packet.getAuthor())));
|
||||||
tag.put(new StringTag("title", packet.getTitle()));
|
tag.put(new StringTag("title", MessageTranslator.convertToPlainText(packet.getTitle())));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -127,12 +128,11 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
|||||||
String title;
|
String title;
|
||||||
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
||||||
// Add title to packet so the server knows we're signing
|
// Add title to packet so the server knows we're signing
|
||||||
if (packet.getTitle().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
|
title = MessageTranslator.convertToPlainText(packet.getTitle());
|
||||||
|
if (title.getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
|
||||||
session.getGeyser().getLogger().warning("Book title larger than server allows!");
|
session.getGeyser().getLogger().warning("Book title larger than server allows!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
title = packet.getTitle();
|
|
||||||
} else {
|
} else {
|
||||||
title = null;
|
title = null;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
|
|
||||||
// Bounding box must be sent after a player dies and respawns since 1.19.40
|
// Bounding box must be sent after a player dies and respawns since 1.19.40
|
||||||
entity.updateBoundingBox();
|
entity.updateBoundingBox();
|
||||||
|
|
||||||
|
// Needed here since 1.19.81 for dimension switching
|
||||||
|
session.getEntityCache().updateBossBars();
|
||||||
break;
|
break;
|
||||||
case START_SWIMMING:
|
case START_SWIMMING:
|
||||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||||
@ -271,8 +274,6 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||||
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
||||||
session.sendUpstreamPacket(attributesPacket);
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
|
|
||||||
session.getEntityCache().updateBossBars();
|
|
||||||
break;
|
break;
|
||||||
case JUMP:
|
case JUMP:
|
||||||
entity.setOnGround(false); // Increase block break time while jumping
|
entity.setOnGround(false); // Increase block break time while jumping
|
||||||
|
@ -184,7 +184,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
BlockStorage blockStorage = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(bedrockId));
|
BlockStorage blockStorage = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(bedrockId));
|
||||||
|
|
||||||
if (BlockRegistries.WATERLOGGED.get().get(javaId)) {
|
if (BlockRegistries.WATERLOGGED.get().get(javaId)) {
|
||||||
BlockStorage waterlogged = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(session.getBlockMappings().getBedrockWater()));
|
BlockStorage waterlogged = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(session.getBlockMappings().getBedrockWater().getRuntimeId()));
|
||||||
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage, waterlogged});
|
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage, waterlogged});
|
||||||
} else {
|
} else {
|
||||||
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage});
|
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage});
|
||||||
|
@ -53,10 +53,16 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundLevelEventPacket packet) {
|
public void translate(GeyserSession session, ClientboundLevelEventPacket packet) {
|
||||||
|
if (!(packet.getEvent() instanceof LevelEventType levelEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Separate case since each RecordEventData in Java is an individual track in Bedrock
|
// Separate case since each RecordEventData in Java is an individual track in Bedrock
|
||||||
if (packet.getEvent() == LevelEvent.RECORD) {
|
if (levelEvent == LevelEventType.RECORD) {
|
||||||
RecordEventData recordEventData = (RecordEventData) packet.getData();
|
RecordEventData recordEventData = (RecordEventData) packet.getData();
|
||||||
SoundEvent soundEvent = Registries.RECORDS.getOrDefault(recordEventData.getRecordId(), SoundEvent.STOP_RECORD);
|
SoundEvent soundEvent = Registries.RECORDS.get(recordEventData.getRecordId());
|
||||||
|
if (soundEvent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Vector3i origin = packet.getPosition();
|
Vector3i origin = packet.getPosition();
|
||||||
Vector3f pos = Vector3f.from(origin.getX() + 0.5f, origin.getY() + 0.5f, origin.getZ() + 0.5f);
|
Vector3f pos = Vector3f.from(origin.getX() + 0.5f, origin.getY() + 0.5f, origin.getZ() + 0.5f);
|
||||||
|
|
||||||
@ -69,19 +75,17 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||||||
levelSoundEvent.setBabySound(false);
|
levelSoundEvent.setBabySound(false);
|
||||||
session.sendUpstreamPacket(levelSoundEvent);
|
session.sendUpstreamPacket(levelSoundEvent);
|
||||||
|
|
||||||
if (soundEvent != SoundEvent.STOP_RECORD) {
|
// Send text packet as it seems to be handled in Java Edition client-side.
|
||||||
// Send text packet as it seems to be handled in Java Edition client-side.
|
TextPacket textPacket = new TextPacket();
|
||||||
TextPacket textPacket = new TextPacket();
|
textPacket.setType(TextPacket.Type.JUKEBOX_POPUP);
|
||||||
textPacket.setType(TextPacket.Type.JUKEBOX_POPUP);
|
textPacket.setNeedsTranslation(true);
|
||||||
textPacket.setNeedsTranslation(true);
|
textPacket.setXuid("");
|
||||||
textPacket.setXuid("");
|
textPacket.setPlatformChatId("");
|
||||||
textPacket.setPlatformChatId("");
|
textPacket.setSourceName(null);
|
||||||
textPacket.setSourceName(null);
|
textPacket.setMessage("record.nowPlaying");
|
||||||
textPacket.setMessage("record.nowPlaying");
|
String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc";
|
||||||
String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc";
|
textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(recordString, session.locale())));
|
||||||
textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(recordString, session.locale())));
|
session.sendUpstreamPacket(textPacket);
|
||||||
session.sendUpstreamPacket(textPacket);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||||||
LevelEventPacket effectPacket = new LevelEventPacket();
|
LevelEventPacket effectPacket = new LevelEventPacket();
|
||||||
effectPacket.setPosition(pos);
|
effectPacket.setPosition(pos);
|
||||||
effectPacket.setData(0);
|
effectPacket.setData(0);
|
||||||
switch (packet.getEvent()) {
|
switch (levelEvent) {
|
||||||
case COMPOSTER -> {
|
case COMPOSTER -> {
|
||||||
effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH);
|
effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH);
|
||||||
|
|
||||||
@ -215,7 +219,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||||||
case BREAK_EYE_OF_ENDER -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EYE_OF_ENDER_DEATH);
|
case BREAK_EYE_OF_ENDER -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_EYE_OF_ENDER_DEATH);
|
||||||
case MOB_SPAWN -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java
|
case MOB_SPAWN -> effectPacket.setType(org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java
|
||||||
case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> {
|
case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> {
|
||||||
effectPacket.setType(packet.getEvent() == LevelEvent.BONEMEAL_GROW ? org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_TURTLE_EGG : org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH);
|
effectPacket.setType(levelEvent == LevelEventType.BONEMEAL_GROW ? org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_TURTLE_EGG : org.cloudburstmc.protocol.bedrock.data.LevelEvent.PARTICLE_CROP_GROWTH);
|
||||||
|
|
||||||
BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData();
|
BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData();
|
||||||
effectPacket.setData(growEventData.getParticleCount());
|
effectPacket.setData(growEventData.getParticleCount());
|
||||||
@ -310,6 +314,17 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||||||
session.sendUpstreamPacket(soundEventPacket);
|
session.sendUpstreamPacket(soundEventPacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case STOP_RECORD -> {
|
||||||
|
LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket();
|
||||||
|
levelSoundEvent.setIdentifier("");
|
||||||
|
levelSoundEvent.setSound(SoundEvent.STOP_RECORD);
|
||||||
|
levelSoundEvent.setPosition(pos);
|
||||||
|
levelSoundEvent.setRelativeVolumeDisabled(false);
|
||||||
|
levelSoundEvent.setExtraData(-1);
|
||||||
|
levelSoundEvent.setBabySound(false);
|
||||||
|
session.sendUpstreamPacket(levelSoundEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
default -> {
|
default -> {
|
||||||
GeyserImpl.getInstance().getLogger().debug("Unhandled level event: " + packet.getEvent());
|
GeyserImpl.getInstance().getLogger().debug("Unhandled level event: " + packet.getEvent());
|
||||||
return;
|
return;
|
||||||
|
@ -42,9 +42,12 @@ import javax.annotation.Nonnull;
|
|||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public final class VersionCheckUtils {
|
public final class VersionCheckUtils {
|
||||||
private static @Nonnull OptionalInt LATEST_BEDROCK_RELEASE = OptionalInt.empty();
|
private static @Nonnull OptionalInt LATEST_BEDROCK_RELEASE = OptionalInt.empty();
|
||||||
|
private static final int SUPPORTED_JAVA_VERSION = 17;
|
||||||
|
|
||||||
public static void checkForOutdatedFloodgate(GeyserLogger logger) {
|
public static void checkForOutdatedFloodgate(GeyserLogger logger) {
|
||||||
try {
|
try {
|
||||||
@ -57,6 +60,34 @@ public final class VersionCheckUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void checkForOutdatedJava(GeyserLogger logger) {
|
||||||
|
// Taken from Paper
|
||||||
|
String javaVersion = System.getProperty("java.version");
|
||||||
|
Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
logger.debug("Could not parse Java version string " + javaVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String version = matcher.group(1);
|
||||||
|
int majorVersion;
|
||||||
|
try {
|
||||||
|
majorVersion = Integer.parseInt(version);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.debug("Could not format as an int: " + version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (majorVersion < SUPPORTED_JAVA_VERSION) {
|
||||||
|
logger.warning("*********************************************");
|
||||||
|
logger.warning("");
|
||||||
|
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_java.header"));
|
||||||
|
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_java.message", SUPPORTED_JAVA_VERSION, javaVersion));
|
||||||
|
logger.warning("");
|
||||||
|
logger.warning("*********************************************");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void checkForGeyserUpdate(Supplier<GeyserCommandSource> recipient) {
|
public static void checkForGeyserUpdate(Supplier<GeyserCommandSource> recipient) {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit d449a0a18549f1b6f2a62e363974019337a26b16
|
Subproject commit 56c3eee7a5241b5609d1936f2a11b05dd1a3d568
|
@ -9,11 +9,11 @@ netty = "4.1.80.Final"
|
|||||||
guava = "29.0-jre"
|
guava = "29.0-jre"
|
||||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||||
websocket = "1.5.1"
|
websocket = "1.5.1"
|
||||||
protocol = "3.0.0.Beta1-20230424.095344-69"
|
protocol = "3.0.0.Beta1-20230507.200054-78"
|
||||||
protocol-connection = "3.0.0.Beta1-20230424.095344-68"
|
protocol-connection = "3.0.0.Beta1-20230507.200054-77"
|
||||||
raknet = "1.0.0.CR1-20230311.162635-3"
|
raknet = "1.0.0.CR1-20230430.211932-7"
|
||||||
mcauthlib = "d9d773e"
|
mcauthlib = "d9d773e"
|
||||||
mcprotocollib = "1.19.4-2-20230426.171506-1"
|
mcprotocollib = "1.19.4-2-20230503.145414-3"
|
||||||
adventure = "4.14.0-20230424.215040-7"
|
adventure = "4.14.0-20230424.215040-7"
|
||||||
adventure-platform = "4.1.2"
|
adventure-platform = "4.1.2"
|
||||||
junit = "5.9.2"
|
junit = "5.9.2"
|
||||||
@ -95,6 +95,7 @@ velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.r
|
|||||||
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
|
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
|
||||||
websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" }
|
websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" }
|
||||||
|
|
||||||
|
protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol-connection" }
|
||||||
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
|
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
|
||||||
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
|
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
|
||||||
|
|
||||||
@ -104,4 +105,4 @@ fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-by
|
|||||||
adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ]
|
adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ]
|
||||||
log4j = [ "log4j-api", "log4j-core", "log4j-slf4j18-impl" ]
|
log4j = [ "log4j-api", "log4j-core", "log4j-slf4j18-impl" ]
|
||||||
jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ]
|
jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ]
|
||||||
protocol = [ "protocol-codec", "protocol-connection" ]
|
protocol = [ "protocol-common", "protocol-codec", "protocol-connection" ]
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren