Move platforms for isolation

This commit is contained in:
Tim203 2024-02-18 19:03:14 +01:00
parent f1f30e5d19
commit bc301fba60
No known key found for this signature in database
56 changed files with 28 additions and 8 deletions

View file

@ -0,0 +1,63 @@
dependencies {
api(projects.core)
api(libs.erosion.bukkit.common) {
isTransitive = false
}
implementation(variantOf(libs.adapters.spigot) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
implementation(libs.commodore)
implementation(libs.adventure.text.serializer.bungeecord)
compileOnly(libs.folia.api)
compileOnly(libs.paper.mojangapi)
implementation(libs.floodgate.spigot)
}
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("com.fasterxml.jackson")
// Relocate net.kyori but exclude the component logger
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
platformRelocate("org.objectweb.asm")
platformRelocate("me.lucko.commodore")
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.viaversion)
provided("com.mojang", "authlib", "1.5.21")
application {
mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain")
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
archiveBaseName.set("Geyser-Spigot")
dependencies {
exclude(dependency("com.google.*:.*"))
// We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
exclude(dependency("io.netty:netty-common:.*"))
exclude(dependency("io.netty:netty-buffer:.*"))
exclude(dependency("io.netty:netty-resolver:.*"))
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
// Commodore includes Brigadier
exclude(dependency("com.mojang:.*"))
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.plugin.Plugin;
import java.util.logging.Logger;
public final class GeyserPaperLogger extends GeyserSpigotLogger {
private final ComponentLogger componentLogger;
public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) {
super(logger, debug);
componentLogger = plugin.getComponentLogger();
}
/**
* Since 1.18.2 this is required so legacy format symbols don't show up in the console for colors
*/
@Override
public void sendMessage(Component message) {
// Done like this so the native component object field isn't relocated
componentLogger.info("{}", PaperAdventure.toNativeComponent(message));
}
static boolean supported() {
try {
Plugin.class.getMethod("getComponentLogger");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
}

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import com.destroystokyo.paper.network.StatusClient;
import org.bukkit.Bukkit;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import java.lang.reflect.Constructor;
import java.net.InetSocketAddress;
/**
* This class is used if possible, so listeners listening for PaperServerListPingEvent exclusively have their changes
* applied.
*/
public final class GeyserPaperPingPassthrough implements IGeyserPingPassthrough {
private static final Constructor<PaperServerListPingEvent> OLD_CONSTRUCTOR = ReflectedNames.getOldPaperPingConstructor();
private final GeyserSpigotLogger logger;
public GeyserPaperPingPassthrough(GeyserSpigotLogger logger) {
this.logger = logger;
}
@SuppressWarnings("deprecation")
@Nullable
@Override
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
try {
// We'd rather *not* use deprecations here, but unfortunately any Adventure class would be relocated at
// runtime because we still have to shade in our own Adventure class. For now.
PaperServerListPingEvent event;
if (OLD_CONSTRUCTOR != null) {
// 1.19, removed in 1.19.4
event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress),
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
} else {
event = new PaperServerListPingEvent(new GeyserStatusClient(inetSocketAddress),
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
}
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
// We have to send a ping, so not really sure what else to do here.
return null;
}
GeyserPingInfo.Players players;
if (event.shouldHidePlayers()) {
players = new GeyserPingInfo.Players(1, 0);
} else {
players = new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers());
}
return new GeyserPingInfo(event.getMotd(), players);
} catch (Exception | LinkageError e) { // LinkageError in the event that method/constructor signatures change
logger.debug("Error while getting Paper ping passthrough: " + e);
return null;
}
}
private record GeyserStatusClient(InetSocketAddress address) implements StatusClient {
@Override
public @NonNull InetSocketAddress getAddress() {
return address;
}
@Override
public int getProtocolVersion() {
return GameProtocol.getJavaProtocolVersion();
}
@Override
public @Nullable InetSocketAddress getVirtualHost() {
return null;
}
}
}

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2019-2021 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.platform.spigot;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.bukkit.Bukkit;
import org.geysermc.geyser.GeyserImpl;
/**
* Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients
* that won't be receiving the data over the network.
* <p>
* As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written.
* If we simply "cancel" or don't forward the packet, then the listener is never called.
*/
public class GeyserSpigotCompressionDisabler extends ChannelOutboundHandlerAdapter {
static final boolean ENABLED;
private static final Class<?> COMPRESSION_PACKET_CLASS;
private static final Class<?> LOGIN_SUCCESS_PACKET_CLASS;
private static final boolean PROTOCOL_SUPPORT_INSTALLED;
static {
PROTOCOL_SUPPORT_INSTALLED = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null;
Class<?> compressionPacketClass = null;
Class<?> loginSuccessPacketClass = null;
boolean enabled = false;
try {
compressionPacketClass = findCompressionPacket();
loginSuccessPacketClass = findLoginSuccessPacket();
enabled = true;
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Could not initialize compression disabler!", e);
}
COMPRESSION_PACKET_CLASS = compressionPacketClass;
LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass;
ENABLED = enabled;
}
public GeyserSpigotCompressionDisabler() {
if (!ENABLED) {
throw new RuntimeException("Geyser compression disabler cannot be initialized in its current state!");
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
Class<?> msgClass = msg.getClass();
// Don't let any compression packet get through
if (!COMPRESSION_PACKET_CLASS.isAssignableFrom(msgClass)) {
if (LOGIN_SUCCESS_PACKET_CLASS.isAssignableFrom(msgClass)) {
if (PROTOCOL_SUPPORT_INSTALLED) {
// ProtocolSupport must send the compression packet, so let's remove what it did before it does damage
if (ctx.pipeline().get("compress") != null) {
ctx.pipeline().remove("compress");
}
if (ctx.pipeline().get("decompress") != null) {
ctx.pipeline().remove("decompress");
}
}
// We're past the point that a compression packet can be sent, so we can safely yeet ourselves away
ctx.channel().pipeline().remove(this);
}
super.write(ctx, msg, promise);
} else if (PROTOCOL_SUPPORT_INSTALLED) {
// We must indicate it "succeeded" or ProtocolSupport will time us out
promise.setSuccess();
}
}
private static Class<?> findCompressionPacket() throws ClassNotFoundException {
try {
return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSetCompression");
} catch (ClassNotFoundException e) {
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
return Class.forName(prefix + ".PacketLoginOutSetCompression");
}
}
private static Class<?> findLoginSuccessPacket() throws ClassNotFoundException {
try {
return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSuccess");
} catch (ClassNotFoundException e) {
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
return Class.forName(prefix + ".PacketLoginOutSuccess");
}
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKeyPath;
// TODO REMOVE
public void loadFloodgate(GeyserSpigotPlugin plugin) {
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
Path geyserDataFolder = plugin.getDataFolder().toPath();
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.ArrayList;
import java.util.List;
@Getter
public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
private final String platformName;
private final String platformVersion;
private final String platformAPIVersion;
private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<PluginInfo> plugins;
@SuppressWarnings("deprecation")
GeyserSpigotDumpInfo() {
super();
this.platformName = Bukkit.getName();
this.platformVersion = Bukkit.getVersion();
this.platformAPIVersion = Bukkit.getBukkitVersion();
this.onlineMode = Bukkit.getOnlineMode();
this.serverIP = Bukkit.getIp();
this.serverPort = Bukkit.getPort();
this.plugins = new ArrayList<>();
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
this.plugins.add(new PluginInfo(plugin.isEnabled(), plugin.getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), plugin.getDescription().getAuthors()));
}
}
}

View file

@ -0,0 +1,199 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.local.LocalAddress;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.bukkit.Bukkit;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.netty.GeyserInjector;
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
import org.geysermc.geyser.network.netty.LocalSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.InetAddress;
import java.util.List;
public class GeyserSpigotInjector extends GeyserInjector {
/**
* Used to determine if ViaVersion is setup to a state where Geyser players will fail at joining if injection is enabled
*/
private final boolean isViaVersion;
/**
* Used to uninject ourselves on shutdown.
*/
private List<ChannelFuture> allServerChannels;
public GeyserSpigotInjector(boolean isViaVersion) {
this.isViaVersion = isViaVersion;
}
@Override
@SuppressWarnings("unchecked")
protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception {
Class<?> serverClazz;
try {
serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
// We're using 1.17+
} catch (ClassNotFoundException e) {
// We're using pre-1.17
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
serverClazz = Class.forName(prefix + ".MinecraftServer");
}
Method getServer = serverClazz.getDeclaredMethod("getServer");
Object server = getServer.invoke(null);
Object connection = null;
// Find the class that manages network IO
for (Method m : serverClazz.getDeclaredMethods()) {
// First is Spigot-mapped name, second is Mojang-mapped name which is implemented as future-proofing
if (m.getReturnType().getSimpleName().equals("ServerConnection") || m.getReturnType().getSimpleName().equals("ServerConnectionListener")) {
if (m.getParameterTypes().length == 0) {
connection = m.invoke(server);
}
}
}
if (connection == null) {
throw new RuntimeException("Unable to find ServerConnection class!");
}
// Find the channel that Minecraft uses to listen to connections
ChannelFuture listeningChannel = null;
for (Field field : connection.getClass().getDeclaredFields()) {
if (field.getType() != List.class) {
continue;
}
field.setAccessible(true);
boolean rightList = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0] == ChannelFuture.class;
if (!rightList) continue;
allServerChannels = (List<ChannelFuture>) field.get(connection);
for (ChannelFuture o : allServerChannels) {
listeningChannel = o;
break;
}
}
if (listeningChannel == null) {
throw new RuntimeException("Unable to find listening channel!");
}
// Making this a function prevents childHandler from being treated as a non-final variable
ChannelInitializer<Channel> childHandler = getChildHandler(bootstrap, listeningChannel);
// This method is what initializes the connection in Java Edition, after Netty is all set.
Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class);
initChannel.setAccessible(true);
ChannelFuture channelFuture = (new ServerBootstrap()
.channel(LocalServerChannelWrapper.class)
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch);
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
}
if (GeyserImpl.getInstance().getConfig().getRemote().authType() == AuthType.FLOODGATE) {
// we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks
ch.pipeline().addBefore(
"packet_handler", "geyser_data_handler",
new SpigotHybridChannelHandler()
);
}
}
})
// Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default
.group(new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser Spigot connection thread", Thread.MAX_PRIORITY)))
.localAddress(LocalAddress.ANY))
.bind()
.syncUninterruptibly();
// We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline
// will work when we add to the list.
allServerChannels.add(channelFuture);
this.localChannel = channelFuture;
this.serverSocketAddress = channelFuture.channel().localAddress();
workAroundWeirdBug(bootstrap);
}
@SuppressWarnings("unchecked")
private ChannelInitializer<Channel> getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) {
List<String> names = listeningChannel.channel().pipeline().names();
ChannelInitializer<Channel> childHandler = null;
for (String name : names) {
ChannelHandler handler = listeningChannel.channel().pipeline().get(name);
try {
Field childHandlerField = handler.getClass().getDeclaredField("childHandler");
childHandlerField.setAccessible(true);
childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler);
// ViaVersion non-Paper-injector workaround so we aren't double-injecting
if (isViaVersion && childHandler instanceof BukkitChannelInitializer) {
childHandler = ((BukkitChannelInitializer) childHandler).original();
}
break;
} catch (Exception e) {
if (bootstrap.getGeyserConfig().isDebugMode()) {
bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
e.printStackTrace();
}
}
}
if (childHandler == null) {
throw new RuntimeException();
}
return childHandler;
}
/**
* Work around an odd bug where the first connection might not initialize all channel handlers on the main pipeline -
* send a dummy status request down that acts as the first connection, then.
* For the future, if someone wants to properly fix this - as of December 28, 2021, it happens on 1.16.5/1.17.1/1.18.1 EXCEPT Spigot 1.16.5
*/
private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
MinecraftProtocol protocol = new MinecraftProtocol();
LocalSession session = new LocalSession(null, bootstrap.getGeyserConfig().getRemote().address(),
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
session.connect();
}
@Override
public void shutdown() {
if (this.allServerChannels != null) {
this.allServerChannels.remove(this.localChannel);
this.allServerChannels = null;
}
super.shutdown();
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.GeyserLogger;
import java.util.logging.Level;
import java.util.logging.Logger;
@AllArgsConstructor
public class GeyserSpigotLogger implements GeyserLogger {
private final Logger logger;
@Getter @Setter
private boolean debug;
@Override
public void severe(String message) {
logger.severe(message);
}
@Override
public void severe(String message, Throwable error) {
logger.log(Level.SEVERE, message, error);
}
@Override
public void error(String message) {
logger.warning(message);
}
@Override
public void error(String message, Throwable error) {
logger.log(Level.WARNING, message, error);
}
@Override
public void warning(String message) {
error(message);
}
@Override
public void info(String message) {
logger.info(message);
}
@Override
public void debug(String message) {
if (debug) {
info(message);
}
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import org.geysermc.geyser.GeyserMain;
public class GeyserSpigotMain extends GeyserMain {
public static void main(String[] args) {
new GeyserSpigotMain().displayMessage();
}
public String getPluginType() {
return "Spigot or Paper (recommended)";
}
public String getPluginFolder() {
return "plugins";
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Iterator;
@AllArgsConstructor
public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
private final GeyserSpigotLogger logger;
@SuppressWarnings("deprecation")
@Override
public @Nullable GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
try {
ServerListPingEvent event = new GeyserPingEvent(inetSocketAddress.getAddress(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
Bukkit.getPluginManager().callEvent(event);
return new GeyserPingInfo(event.getMotd(), event.getMaxPlayers(), event.getNumPlayers());
} catch (Exception | LinkageError e) { // LinkageError in the event that method/constructor signatures change
logger.debug("Error while getting Bukkit ping passthrough: " + e);
return null;
}
}
// These methods are unimplemented on spigot api by default so we add stubs so plugins don't complain
private static class GeyserPingEvent extends ServerListPingEvent {
public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) {
super("", address, motd, numPlayers, maxPlayers);
}
@Override
public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException {
}
@NonNull
@Override
public Iterator<Player> iterator() throws UnsupportedOperationException {
return Collections.emptyIterator();
}
}
}

View file

@ -0,0 +1,496 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginCommand;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.core.skin.SkinApplier;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotInjector geyserInjector;
private GeyserSpigotLogger geyserLogger;
private IGeyserPingPassthrough geyserSpigotPingPassthrough;
private GeyserSpigotWorldManager geyserWorldManager;
private GeyserImpl geyser;
/**
* The Minecraft server version, formatted as <code>1.#.#</code>
*/
private String minecraftVersion;
@Override
public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this);
try {
// AvailableCommandsSerializer_v291 complains otherwise - affects at least 1.8
ByteBuf.class.getMethod("writeShortLE", int.class);
// Only available in 1.13.x
Class.forName("org.bukkit.event.server.ServerLoadEvent");
// We depend on this as a fallback in certain scenarios
BlockData.class.getMethod("getAsString");
} catch (ClassNotFoundException | NoSuchMethodException e) {
getLogger().severe("*********************************************");
getLogger().severe("");
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
getLogger().severe("");
getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
try {
Class.forName("net.md_5.bungee.chat.ComponentSerializer");
} catch (ClassNotFoundException e) {
if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat
getLogger().severe("*********************************************");
getLogger().severe("");
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
getLogger().severe("");
getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
}
try {
Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator");
} catch (ClassNotFoundException e) {
getLogger().severe("*********************************************");
getLogger().severe("");
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
getLogger().severe("");
getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
if (!loadConfig()) {
return;
}
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this, null);
}
@Override
public void onEnable() {
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
geyserLogger.severe("WHY DO YOU HAVE FLOODGATE INSTALLED!!!!!!! REMOVE IT!!!!");
}
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
// Because Bukkit locks its command map upon startup, we need to
// add our plugin commands in onEnable, but populating the executor
// can happen at any time (later in #onGeyserEnable())
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
}
}
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onServerLoaded(ServerLoadEvent event) {
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
geyser.setShuttingDown(false);
}
onGeyserEnable();
}
}, this);
}
public void onGeyserEnable() {
// Configs are loaded once early - so we can create the logger, then load extensions and finally register
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
}
GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
if (ReflectedNames.checkPaperPingEvent()) {
this.geyserSpigotPingPassthrough = new GeyserPaperPingPassthrough(geyserLogger);
} else if (ReflectedNames.newSpigotPingConstructorExists()) {
this.geyserSpigotPingPassthrough = new GeyserSpigotPingPassthrough(geyserLogger);
} else {
// Can't enable one of the other options
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
}
}
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
// Don't need to re-create the world manager/re-register commands/reinject when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
this.geyserInjector.initializeLocalChannel(this);
if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) {
try {
String name = Bukkit.getServer().getClass().getPackage().getName();
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
SpigotAdapters.registerWorldAdapter(nmsVersion);
if (isViaVersion && isViaVersionNeeded()) {
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
} else {
// No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
} catch (Exception e) {
if (geyserConfig.isDebugMode()) {
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
e.printStackTrace();
}
}
} else {
geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
}
if (this.geyserWorldManager == null) {
// No NMS adapter
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
geyserLogger.debug("Using default world manager.");
}
PluginCommand geyserCommand = this.getCommand("geyser");
Objects.requireNonNull(geyserCommand, "base command cannot be null");
geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> commands = entry.getValue();
if (commands.isEmpty()) {
continue;
}
PluginCommand command = this.getCommand(entry.getKey().description().id());
if (command == null) {
continue;
}
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
}
// Register permissions so they appear in, for example, LuckPerms' UI
// Re-registering permissions throws an error
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
}
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
}
}
@Override
public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
if (geyserInjector != null) {
geyserInjector.shutdown();
}
}
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override
public GeyserSpigotConfiguration getGeyserConfig() {
return geyserConfig;
}
@Override
public GeyserSpigotLogger getGeyserLogger() {
return geyserLogger;
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return this.geyserCommandManager;
}
@Override
public IGeyserPingPassthrough getGeyserPingPassthrough() {
return geyserSpigotPingPassthrough;
}
@Override
public WorldManager getWorldManager() {
return this.geyserWorldManager;
}
@Override
public Path getConfigFolder() {
return getDataFolder().toPath();
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserSpigotDumpInfo();
}
@Override
public String getMinecraftServerVersion() {
return this.minecraftVersion;
}
@Override
public SocketAddress getSocketAddress() {
return this.geyserInjector.getServerSocketAddress();
}
@Override
public SkinApplier createSkinApplier() {
// return new SpigotSkinApplier(new SpigotVersionSpecificMethods(this), this);
return null;
}
/**
* @return the server version before ViaVersion finishes initializing
*/
public ProtocolVersion getServerProtocolVersion() {
return ProtocolVersion.getClosest(this.minecraftVersion);
}
/**
* This function should not run unless ViaVersion is installed on the server.
*
* @return true if there is any block mappings difference between the server and client.
*/
private boolean isViaVersionNeeded() {
ProtocolVersion serverVersion = getServerProtocolVersion();
List<ProtocolPathEntry> protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(),
serverVersion.getVersion());
if (protocolList == null) {
// No translation needed!
return false;
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
if (mappingData != null) {
return true;
}
}
// All mapping data is null, which means client and server block states are the same
return false;
}
@NonNull
@Override
public String getServerBindAddress() {
return Bukkit.getIp();
}
@Override
public int getServerPort() {
return Bukkit.getPort();
}
@Override
public boolean testFloodgatePluginPresent() {
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
geyserConfig.loadFloodgate(this);
return true;
}
return false;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
try {
if (!getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
}
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
} catch (IOException ex) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return false;
}
return true;
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserSpigotUpdateListener implements Listener {
@EventHandler
public void onPlayerJoin(final PlayerJoinEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
final Player player = event.getPlayer();
if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSource(player));
}
}
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.viaversion.viaversion.api.Via;
import org.bukkit.Bukkit;
import org.bukkit.UnsafeValues;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.text.GeyserLocale;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public final class GeyserSpigotVersionChecker {
private static final String VIAVERSION_DOWNLOAD_URL = "https://ci.viaversion.com/job/ViaVersion/";
@SuppressWarnings("deprecation")
public static void checkForSupportedProtocol(GeyserLogger logger, boolean viaversion) {
if (viaversion) {
checkViaVersionSupportedVersions(logger);
return;
}
try {
// This method is only present on later versions of Paper
UnsafeValues.class.getMethod("getProtocolVersion");
if (Bukkit.getUnsafe().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) {
sendOutdatedMessage(logger);
}
return;
} catch (NoSuchMethodException ignored) {
}
// Otherwise, we can just try to find the SharedConstants class
// It isn't present in all server versions, but if we can't find it, then we're probably not in the latest version
Class<?> sharedConstants;
try {
sharedConstants = Class.forName("net.minecraft.SharedConstants");
} catch (ClassNotFoundException e) {
// We're using pre-1.17
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
try {
sharedConstants = Class.forName(prefix + ".SharedConstants");
} catch (ClassNotFoundException e2) {
sendOutdatedMessage(logger);
return;
}
}
for (Method method : sharedConstants.getMethods()) {
if (method.getReturnType() == int.class && Modifier.isStatic(method.getModifiers())) {
int protocolVersion;
try {
protocolVersion = (int) method.invoke(null);
} catch (IllegalAccessException | InvocationTargetException e) {
logger.warning("Could not determine server version! This is safe to ignore, but please report to the developers: " + e.getMessage());
if (logger.isDebug()) {
e.printStackTrace();
}
return;
}
if (protocolVersion != GameProtocol.getJavaProtocolVersion()) {
sendOutdatedMessage(logger);
}
return;
}
}
sendOutdatedMessage(logger);
}
private static void checkViaVersionSupportedVersions(GeyserLogger logger) {
// Run after ViaVersion has obtained the server protocol version
Via.getPlatform().runSync(() -> {
if (Via.getAPI().getSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) {
// Via supports this protocol version; we will be able to connect.
return;
}
if (Via.getAPI().getFullSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) {
// ViaVersion supports our protocol, but the user has blocked them from connecting.
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.blocked", GameProtocol.getAllSupportedJavaVersions()));
return;
}
// Else, presumably, ViaVersion is not updated.
sendOutdatedViaVersionMessage(logger);
});
}
public static void sendOutdatedViaVersionMessage(GeyserLogger logger) {
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.too_old",
VIAVERSION_DOWNLOAD_URL));
}
private static void sendOutdatedMessage(GeyserLogger logger) {
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.no_supported_protocol", GameProtocol.getAllSupportedJavaVersions(), VIAVERSION_DOWNLOAD_URL));
}
private GeyserSpigotVersionChecker() {
}
}

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Utility class for converting our shaded Adventure into the Adventure bundled in Paper.
* <p>
* Code mostly taken from <a href="https://github.com/KyoriPowered/adventure-platform/blob/94d5821f2e755170f42bd8a5fe1d5bf6f66d04ad/platform-bukkit/src/main/java/net/kyori/adventure/platform/bukkit/PaperFacet.java#L46">here</a>
* and the MinecraftReflection class.
*/
public final class PaperAdventure {
private static final MethodHandle NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND;
private static final Method SEND_MESSAGE_COMPONENT;
static {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle nativeGsonComponentSerializerDeserializeMethodBound = null;
// String.join because otherwise the class name will be relocated
final Class<?> nativeGsonComponentSerializerClass = findClass(String.join(".",
"net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializer"));
final Class<?> nativeGsonComponentSerializerImplClass = findClass(String.join(".",
"net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializerImpl"));
if (nativeGsonComponentSerializerClass != null && nativeGsonComponentSerializerImplClass != null) {
MethodHandle nativeGsonComponentSerializerGsonGetter = null;
try {
nativeGsonComponentSerializerGsonGetter = lookup.findStatic(nativeGsonComponentSerializerClass,
"gson", MethodType.methodType(nativeGsonComponentSerializerClass));
} catch (final NoSuchMethodException | IllegalAccessException ignored) {
}
MethodHandle nativeGsonComponentSerializerDeserializeMethod = null;
try {
final Method method = nativeGsonComponentSerializerImplClass.getDeclaredMethod("deserialize", String.class);
method.setAccessible(true);
nativeGsonComponentSerializerDeserializeMethod = lookup.unreflect(method);
} catch (final NoSuchMethodException | IllegalAccessException ignored) {
}
if (nativeGsonComponentSerializerGsonGetter != null) {
if (nativeGsonComponentSerializerDeserializeMethod != null) {
try {
nativeGsonComponentSerializerDeserializeMethodBound = nativeGsonComponentSerializerDeserializeMethod
.bindTo(nativeGsonComponentSerializerGsonGetter.invoke());
} catch (final Throwable throwable) {
GeyserImpl.getInstance().getLogger().error("Failed to access native GsonComponentSerializer", throwable);
}
}
}
}
NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND = nativeGsonComponentSerializerDeserializeMethodBound;
Method playerComponentSendMessage = null;
final Class<?> nativeComponentClass = findClass(String.join(".",
"net", "kyori", "adventure", "text", "Component"));
if (nativeComponentClass != null) {
try {
playerComponentSendMessage = CommandSender.class.getMethod("sendMessage", nativeComponentClass);
} catch (final NoSuchMethodException e) {
if (GeyserImpl.getInstance().getLogger().isDebug()) {
e.printStackTrace();
}
}
}
SEND_MESSAGE_COMPONENT = playerComponentSendMessage;
}
public static @Nullable Object toNativeComponent(final Component component) {
if (NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND == null) {
GeyserImpl.getInstance().getLogger().error("Illegal state where Component serialization was called when it wasn't available!");
return null;
}
try {
return NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND.invoke(DefaultComponentSerializer.get().serialize(component));
} catch (final Throwable throwable) {
GeyserImpl.getInstance().getLogger().error("Failed to create native Component message", throwable);
return null;
}
}
public static void sendMessage(final CommandSender sender, final Component component) {
if (SEND_MESSAGE_COMPONENT == null) {
GeyserImpl.getInstance().getLogger().error("Illegal state where Component sendMessage was called when it wasn't available!");
return;
}
final Object nativeComponent = toNativeComponent(component);
if (nativeComponent != null) {
try {
SEND_MESSAGE_COMPONENT.invoke(sender, nativeComponent);
} catch (final InvocationTargetException | IllegalAccessException e) {
GeyserImpl.getInstance().getLogger().error("Failed to send native Component message", e);
}
}
}
public static boolean canSendMessageUsingComponent() {
return SEND_MESSAGE_COMPONENT != null;
}
/**
* Gets a class by the first name available.
*
* @return a class or {@code null} if not found
*/
private static @Nullable Class<?> findClass(final String className) {
try {
return Class.forName(className);
} catch (final ClassNotFoundException ignored) {
}
return null;
}
private PaperAdventure() {
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import com.destroystokyo.paper.network.StatusClient;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
/**
* A utility class for checking on the existence of classes, constructors, fields, methods
*/
public final class ReflectedNames {
static boolean checkPaperPingEvent() {
try {
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
static boolean newSpigotPingConstructorExists() {
return getConstructor(ServerListPingEvent.class, InetAddress.class, String.class, boolean.class, int.class, int.class) != null;
}
static @Nullable Constructor<PaperServerListPingEvent> getOldPaperPingConstructor() {
if (getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, int.class,
int.class, String.class, int.class, CachedServerIcon.class) != null) {
// @NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers,
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
// New constructor is present
return null;
}
// @NotNull StatusClient client, @NotNull String motd, boolean shouldSendChatPreviews, int numPlayers, int maxPlayers,
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
return getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, boolean.class, int.class, int.class,
String.class, int.class, CachedServerIcon.class);
}
/**
* @return if this class has a constructor with the specified arguments
*/
@Nullable
private static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... args) {
try {
return clazz.getConstructor(args);
} catch (NoSuchMethodException e) {
return null;
}
}
private ReflectedNames() {
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot;
import com.mojang.authlib.GameProfile;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.geysermc.api.connection.Connection;
import org.geysermc.floodgate.spigot.util.ClassNames;
import org.geysermc.geyser.floodgate.IntegratedFloodgateProvider;
import javax.annotation.Nonnull;
import static org.geysermc.floodgate.core.util.ReflectionUtils.setValue;
@ChannelHandler.Sharable
public final class SpigotHybridChannelHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(@Nonnull ChannelHandlerContext ctx, @Nonnull Object packet) throws Exception {
Connection session = ctx.channel().attr(IntegratedFloodgateProvider.SESSION_KEY).get();
// TODO generify this code within Floodgate
if (ClassNames.LOGIN_START_PACKET.isInstance(packet)) {
Object networkManager = ctx.channel().pipeline().get("packet_handler");
Object packetListener = ClassNames.PACKET_LISTENER.get(networkManager);
setValue(networkManager, "spoofedUUID", session.javaUuid());
// check if the server is actually in the Login state
if (!ClassNames.LOGIN_LISTENER.isInstance(packetListener)) {
// player is not in the login state, abort
// I would've liked to close the channel for security reasons, but our big friend
// ProtocolSupport, who likes to break things, doesn't work otherwise
ctx.pipeline().remove(this);
return;
}
// set the player his GameProfile, we can't change the username without this
GameProfile gameProfile = new GameProfile(
// TODO testing only
session.javaUuid(), session.javaUsername()
);
setValue(packetListener, ClassNames.LOGIN_PROFILE, gameProfile);
// we have to fake the offline player (login) cycle
// just like on Spigot:
// LoginListener#initUUID
// new LoginHandler().fireEvents();
// and the tick of LoginListener will do the rest
ClassNames.INIT_UUID.invoke(packetListener);
Object loginHandler = ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener);
ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler);
ctx.pipeline().remove(this);
return;
}
ctx.fireChannelRead(packet);
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
/**
* Needs to be a separate class so pre-1.13 loads correctly.
*/
public final class GeyserBrigadierSupport {
public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
// Enable command completions if supported
// This is beneficial because this is sent over the network and Bedrock can see it
Commodore commodore = CommodoreProvider.getCommodore(plugin);
LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
builder.then(LiteralArgumentBuilder.literal(command));
}
commodore.register(pluginCommand, builder);
try {
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
} catch (ClassNotFoundException e) {
plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
}
}
private GeyserBrigadierSupport() {
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.command;
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
import com.mojang.brigadier.tree.CommandNode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Map;
public final class GeyserPaperCommandListener implements Listener {
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
// Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
// event.hasFiredAsync is never true
if (event.isAsynchronous()) {
CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
if (geyserBrigadier != null) {
Player player = event.getPlayer();
boolean isJavaPlayer = isProbablyJavaPlayer(player);
Map<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
while (it.hasNext()) {
CommandNode<?> subnode = it.next();
Command command = commands.get(subnode.getName());
if (command != null) {
if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) {
// Remove this from the node as we don't have permission to use it
it.remove();
}
}
}
}
}
}
/**
* This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
* chance, though.
*/
private boolean isProbablyJavaPlayer(Player player) {
if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
// For sure this is a Bedrock player
return false;
}
if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
InetSocketAddress address = player.getAddress();
if (address != null) {
return address.getPort() != 0;
}
}
return true;
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.command;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor {
public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
super(geyser, commands);
}
@Override
public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
SpigotCommandSource commandSender = new SpigotCommandSource(sender);
GeyserSession session = getGeyserSession(commandSender);
if (args.length > 0) {
GeyserCommand geyserCommand = getCommand(args[0]);
if (geyserCommand != null) {
if (!sender.hasPermission(geyserCommand.permission())) {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
return true;
}
if (geyserCommand.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()));
return true;
}
geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
return true;
} else {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
}
} else {
getCommand("help").execute(session, commandSender, new String[0]);
return true;
}
return true;
}
@Override
public List<String> onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
if (args.length == 1) {
return tabComplete(new SpigotCommandSource(sender));
}
return Collections.emptyList();
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.command;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
import java.lang.reflect.Field;
public class GeyserSpigotCommandManager extends GeyserCommandManager {
private static final CommandMap COMMAND_MAP;
static {
CommandMap commandMap = null;
try {
// Paper-only
Server.class.getMethod("getCommandMap");
commandMap = Bukkit.getServer().getCommandMap();
} catch (NoSuchMethodException e) {
try {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
COMMAND_MAP = commandMap;
}
public GeyserSpigotCommandManager(GeyserImpl geyser) {
super(geyser);
}
@Override
public String description(String command) {
Command cmd = COMMAND_MAP.getCommand(command.replace("/", ""));
return cmd != null ? cmd.getDescription() : "";
}
public static CommandMap getCommandMap() {
return COMMAND_MAP;
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.spigot.PaperAdventure;
import org.geysermc.geyser.text.GeyserLocale;
public class SpigotCommandSource implements GeyserCommandSource {
private final org.bukkit.command.CommandSender handle;
public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
this.handle = handle;
// Ensure even Java players' languages are loaded
GeyserLocale.loadGeyserLocale(locale());
}
@Override
public String name() {
return handle.getName();
}
@Override
public void sendMessage(@NonNull String message) {
handle.sendMessage(message);
}
@SuppressWarnings("deprecation")
@Override
public void sendMessage(Component message) {
if (PaperAdventure.canSendMessageUsingComponent()) {
PaperAdventure.sendMessage(handle, message);
return;
}
// CommandSender#sendMessage(BaseComponent[]) is Paper-only
handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message));
}
@Override
public boolean isConsole() {
return handle instanceof ConsoleCommandSender;
}
@SuppressWarnings("deprecation")
@Override
public String locale() {
if (this.handle instanceof Player player) {
// getLocale() is deprecated on Paper, but not on Spigot
return player.getLocale();
}
return GeyserLocale.getDefaultLocale();
}
@Override
public boolean hasPermission(String permission) {
return handle.hasPermission(permission);
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
import org.cloudburstmc.math.vector.Vector3i;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class GeyserPistonListener implements Listener {
private final GeyserImpl geyser;
private final GeyserSpigotWorldManager worldManager;
public GeyserPistonListener(GeyserImpl geyser, GeyserSpigotWorldManager worldManager) {
this.geyser = geyser;
this.worldManager = worldManager;
}
// The handlers' parent class cannot be registered
@EventHandler(priority = EventPriority.MONITOR)
public void onPistonExtend(BlockPistonExtendEvent event) {
onPistonAction(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPistonRetract(BlockPistonRetractEvent event) {
onPistonAction(event);
}
private void onPistonAction(BlockPistonEvent event) {
if (event.isCancelled()) {
return;
}
World world = event.getBlock().getWorld();
boolean isExtend = event instanceof BlockPistonExtendEvent;
Location location = event.getBlock().getLocation();
Vector3i position = getVector(location);
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
boolean sticky = event.isSticky();
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
boolean blocksFilled = false;
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {
Player player = Bukkit.getPlayer(entry.getKey());
if (player == null || !player.getWorld().equals(world)) {
continue;
}
GeyserSession session = entry.getValue();
int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 4;
int dZ = Math.abs(location.getBlockZ() - player.getLocation().getBlockZ()) >> 4;
if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
// Ignore pistons outside the player's render distance
continue;
}
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
// being returned instead.
if (!blocksFilled) {
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
for (Block block : blocks) {
Location attachedLocation = block.getLocation();
int blockId = worldManager.getBlockNetworkId(block);
// Ignore blocks that will be destroyed
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
attachedBlocks.put(getVector(attachedLocation), blockId);
}
}
blocksFilled = true;
}
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
// event.getDirection() is unreliable
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache();
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
new PistonBlockEntity(session, position, orientation, sticky, !isExtend));
blockEntity.setAction(type, attachedBlocks);
});
}
}
private Vector3i getVector(Location location) {
return Vector3i.from(location.getX(), location.getY(), location.getZ());
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.world;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import lombok.AllArgsConstructor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
@AllArgsConstructor
public class GeyserSpigotBlockPlaceListener implements Listener {
private final GeyserImpl geyser;
private final GeyserSpigotWorldManager worldManager;
@EventHandler
public void place(final BlockPlaceEvent event) {
GeyserSession session = geyser.connectionByUuid(event.getPlayer().getUniqueId());
if (session == null) {
return;
}
LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()));
placeBlockSoundPacket.setBabySound(false);
if (worldManager.isLegacy()) {
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(worldManager.getBlockAt(session,
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
} else {
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID)));
}
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null);
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.world.manager;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntList;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
import org.geysermc.geyser.session.GeyserSession;
import java.util.List;
import java.util.Objects;
/**
* Used when block IDs need to be translated to the latest version
*/
public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager {
private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) {
super(plugin);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
List<ProtocolPathEntry> protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(),
serverVersion.getVersion());
Objects.requireNonNull(protocolList, "protocolList cannot be null");
for (int oldBlockId : allBlockStates) {
int newBlockId = oldBlockId;
// protocolList should *not* be null; we checked for that before initializing this class
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
if (mappingData != null) {
newBlockId = mappingData.getNewBlockStateId(newBlockId);
}
}
oldToNewBlockId.put(oldBlockId, newBlockId);
}
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
int nativeBlockId = super.getBlockAt(session, x, y, z);
return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId);
}
@Override
public boolean isLegacy() {
return true;
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
public GeyserSpigotNativeWorldManager(Plugin plugin) {
super(plugin);
adapter = SpigotAdapters.getWorldAdapter();
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) {
return BlockStateValues.JAVA_AIR_ID;
}
return adapter.getBlockAt(player.getWorld(), x, y, z);
}
@Nullable
@Override
public String[] getBiomeIdentifiers(boolean withTags) {
// Biome identifiers will basically always be the same for one server, since you have to re-send the
// ClientboundLoginPacket to change the registry. Therefore, don't bother caching for each player.
return adapter.getBiomeSuggestions(withTags);
}
}

View file

@ -0,0 +1,231 @@
/*
* Copyright (c) 2019-2022 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.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.erosion.bukkit.BukkitLecterns;
import org.geysermc.erosion.bukkit.BukkitUtils;
import org.geysermc.erosion.bukkit.PickBlockUtils;
import org.geysermc.erosion.bukkit.SchedulerUtils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.GameRule;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* The base world manager to use when there is no supported NMS revision
*/
public class GeyserSpigotWorldManager extends WorldManager {
private final Plugin plugin;
private final BukkitLecterns lecterns;
public GeyserSpigotWorldManager(Plugin plugin) {
this.plugin = plugin;
this.lecterns = new BukkitLecterns(plugin);
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return BlockStateValues.JAVA_AIR_ID;
}
World world = bukkitPlayer.getWorld();
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
// If the chunk isn't loaded, how could we even be here?
return BlockStateValues.JAVA_AIR_ID;
}
return getBlockNetworkId(world.getBlockAt(x, y, z));
}
public int getBlockNetworkId(Block block) {
if (SchedulerUtils.FOLIA && !Bukkit.isOwnedByCurrentRegion(block)) {
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
CompletableFuture<String> blockData = new CompletableFuture<>();
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID);
}
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
}
@Override
public boolean hasOwnChunkCache() {
return true;
}
@Override
public void sendLecternData(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Run as a task to prevent async issues
SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
}
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
if (SchedulerUtils.FOLIA) {
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
if (chunk == null) {
return;
}
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
sendLecternData(session, chunk, blockEntityInfos));
} else {
Bukkit.getScheduler().runTask(this.plugin, () -> {
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
if (chunk == null) {
return;
}
sendLecternData(session, chunk, blockEntityInfos);
});
}
}
private @Nullable Chunk getChunk(World world, int x, int z) {
if (!world.isChunkLoaded(x, z)) {
return null;
}
return world.getChunkAt(x, z);
}
private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
//noinspection ForLoopReplaceableByForEach - avoid constructing Iterator
for (int i = 0; i < blockEntityInfos.size(); i++) {
BlockEntityInfo info = blockEntityInfos.get(i);
Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
sendLecternData(session, block, true);
}
}
private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
if (blockEntityTag != null) {
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
}
}
@Override
public boolean shouldExpectLecternHandled(GeyserSession session) {
return true;
}
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
if (bukkitGameRule == null) {
GeyserImpl.getInstance().getLogger().debug("Unknown game rule " + gameRule.getJavaID());
return gameRule.getDefaultBooleanValue();
}
Player bukkitPlayer = Objects.requireNonNull(Bukkit.getPlayer(session.getPlayerEntity().getUuid()));
Object value = bukkitPlayer.getWorld().getGameRuleValue(bukkitGameRule);
if (value instanceof Boolean booleanValue) {
return booleanValue;
}
GeyserImpl.getInstance().getLogger().debug("Expected a bool for " + gameRule + " but got " + value);
return gameRule.getDefaultBooleanValue();
}
@Override
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
if (bukkitGameRule == null) {
GeyserImpl.getInstance().getLogger().debug("Unknown game rule " + gameRule.getJavaID());
return gameRule.getDefaultIntValue();
}
Player bukkitPlayer = Objects.requireNonNull(Bukkit.getPlayer(session.getPlayerEntity().getUuid()));
Object value = bukkitPlayer.getWorld().getGameRuleValue(bukkitGameRule);
if (value instanceof Integer intValue) {
return intValue;
}
GeyserImpl.getInstance().getLogger().debug("Expected an int for " + gameRule + " but got " + value);
return gameRule.getDefaultIntValue();
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
Player player = Bukkit.getPlayer(session.javaUuid());
if (player != null) {
return player.hasPermission(permission);
}
return false;
}
@Override
public @NonNull CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
future.complete(null);
return future;
}
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access?
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
return future;
}
/**
* This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id
* to the current one.
*
* @return whether there is a difference between client block state and server block state that requires extra processing
*/
public boolean isLegacy() {
return false;
}
}

View file

@ -0,0 +1,16 @@
main: org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin
name: ${name}-Spigot
author: ${author}
website: ${url}
version: ${version}
softdepend: ["ViaVersion", "floodgate"]
api-version: 1.13
folia-supported: true
commands:
geyser:
description: The main command for Geyser.
usage: /geyser <subcommand>
permission: geyser.command
permissions:
geyser.command:
default: true