Cloud for commands (#3808)

Co-authored-by: onebeastchris <github@onechris.mozmail.com>
This commit is contained in:
Konicai 2024-07-11 23:56:42 -05:00
parent 6002c9c7a1
commit 87ab51cb28
95 changed files with 2556 additions and 1879 deletions

View file

@ -1,7 +1,3 @@
plugins {
application
}
architectury {
platformSetupLoomIde()
fabric()
@ -35,13 +31,12 @@ dependencies {
shadow(projects.api) { isTransitive = false }
shadow(projects.common) { isTransitive = false }
// Permissions
modImplementation(libs.fabric.permissions)
include(libs.fabric.permissions)
modImplementation(libs.cloud.fabric)
include(libs.cloud.fabric)
}
application {
mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain"
}
relocate("org.cloudburstmc.netty")

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.platform.fabric;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
@ -34,9 +33,16 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.fabric.FabricServerCommandManager;
public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
@ -70,20 +76,23 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
this.onGeyserInitialize();
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
() -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
ModCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new FabricServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
}
@Override
public boolean isServer() {
return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
}
@Override
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
return Permissions.check(source, permissionNode);
}
@Override
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
return Permissions.check(source, permissionNode, permissionLevel);
}
}

View file

@ -1,7 +1,3 @@
plugins {
application
}
// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
// NeoForge's class loader is *really* annoying.
provided("org.cloudburstmc.math", "api")
@ -38,10 +34,13 @@ dependencies {
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)
modImplementation(libs.cloud.neoforge)
include(libs.cloud.neoforge)
}
application {
mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain"
}
tasks {

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLLoader;
@ -35,15 +36,22 @@ import net.neoforged.neoforge.event.GameShuttingDownEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.neoforge.NeoForgeServerCommandManager;
import java.util.Objects;
@Mod(ModConstants.MOD_ID)
public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
public GeyserNeoForgeBootstrap(ModContainer container) {
super(new GeyserNeoForgePlatform(container));
@ -56,9 +64,25 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
NeoForge.EVENT_BUS.addListener(this::onServerStopping);
NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onPermissionGather);
this.onGeyserInitialize();
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
() -> getServer().createCommandSourceStack(),
ModCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new NeoForgeServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud);
this.setCommandRegistry(registry);
NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined);
}
private void onServerStarted(ServerStartedEvent event) {
@ -87,13 +111,17 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
return FMLLoader.getDist().isDedicatedServer();
}
@Override
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
return this.permissionHandler.hasPermission(source, permissionNode);
}
private void onPermissionGather(PermissionGatherEvent.Nodes event) {
getGeyser().eventBus().fire(
(GeyserRegisterPermissionsEvent) (permission, defaultValue) -> {
Objects.requireNonNull(permission, "permission");
Objects.requireNonNull(defaultValue, "permission default for " + permission);
@Override
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
if (permission.isBlank()) {
return;
}
PermissionUtils.register(permission, defaultValue, event);
}
);
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2019-2024 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.neoforge;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.neoforge.PermissionNotRegisteredException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class GeyserNeoForgeCommandRegistry extends CommandRegistry {
/**
* Permissions with an undefined permission default. Use Set to not register the same fallback more than once.
* NeoForge requires that all permissions are registered, and cloud-neoforge follows that.
* This is unlike most platforms, on which we wouldn't register a permission if no default was provided.
*/
private final Set<String> undefinedPermissions = new HashSet<>();
public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
super(geyser, cloud);
}
@Override
protected void register(GeyserCommand command, Map<String, GeyserCommand> commands) {
super.register(command, commands);
// FIRST STAGE: Collect all permissions that may have undefined defaults.
if (!command.permission().isBlank() && command.permissionDefault() == null) {
// Permission requirement exists but no default value specified.
undefinedPermissions.add(command.permission());
}
}
@Override
protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
super.onRegisterPermissions(event);
// SECOND STAGE
// Now that we are aware of all commands, we can eliminate some incorrect assumptions.
// Example: two commands may have the same permission, but only of them defines a permission default.
undefinedPermissions.removeAll(permissionDefaults.keySet());
}
/**
* Registers permissions with possibly undefined defaults.
* Should be subscribed late to allow extensions and mods to register a desired permission default first.
*/
void onPermissionGatherForUndefined(PermissionGatherEvent.Nodes event) {
// THIRD STAGE
for (String permission : undefinedPermissions) {
if (PermissionUtils.register(permission, TriState.NOT_SET, event)) {
// The permission was not already registered
geyser.getLogger().debug("Registered permission " + permission + " with fallback default value of NOT_SET");
}
}
}
@Override
public boolean hasPermission(GeyserCommandSource source, String permission) {
// NeoForgeServerCommandManager will throw this exception if the permission is not registered to the server.
// We can't realistically ensure that every permission is registered (calls by API users), so we catch this.
// This works for our calls, but not for cloud's internal usage. For that case, see above.
try {
return super.hasPermission(source, permission);
} catch (PermissionNotRegisteredException e) {
return false;
}
}
}

View file

@ -1,149 +0,0 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.neoforged.neoforge.server.permission.PermissionAPI;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommandManager;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class GeyserNeoForgePermissionHandler {
private static final Constructor<?> PERMISSION_NODE_CONSTRUCTOR;
static {
try {
@SuppressWarnings("rawtypes")
Constructor<PermissionNode> constructor = PermissionNode.class.getDeclaredConstructor(
String.class,
PermissionType.class,
PermissionNode.PermissionResolver.class,
PermissionDynamicContextKey[].class
);
constructor.setAccessible(true);
PERMISSION_NODE_CONSTRUCTOR = constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to construct PermissionNode!", e);
}
}
private final Map<String, PermissionNode<Boolean>> permissionNodes = new HashMap<>();
public void onPermissionGather(PermissionGatherEvent.Nodes event) {
this.registerNode(Constants.UPDATE_PERMISSION, event);
GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
for (Map.Entry<String, Command> entry : commandManager.commands().entrySet()) {
Command command = entry.getValue();
// Don't register aliases
if (!command.name().equals(entry.getKey())) {
continue;
}
this.registerNode(command.permission(), event);
}
for (Map<String, Command> commands : commandManager.extensionCommands().values()) {
for (Map.Entry<String, Command> entry : commands.entrySet()) {
Command command = entry.getValue();
// Don't register aliases
if (!command.name().equals(entry.getKey())) {
continue;
}
this.registerNode(command.permission(), event);
}
}
}
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
PermissionNode<Boolean> node = this.permissionNodes.get(permissionNode);
if (node == null) {
GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode);
return false;
}
return PermissionAPI.getPermission((ServerPlayer) source, node);
}
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
if (!source.isPlayer()) {
return true;
}
assert source.getPlayer() != null;
boolean permission = this.hasPermission(source.getPlayer(), permissionNode);
if (!permission) {
return source.getPlayer().hasPermissions(permissionLevel);
}
return true;
}
private void registerNode(String node, PermissionGatherEvent.Nodes event) {
PermissionNode<Boolean> permissionNode = this.createNode(node);
// NeoForge likes to crash if you try and register a duplicate node
if (!event.getNodes().contains(permissionNode)) {
event.addNodes(permissionNode);
this.permissionNodes.put(node, permissionNode);
}
}
@SuppressWarnings("unchecked")
private PermissionNode<Boolean> createNode(String node) {
// The typical constructors in PermissionNode require a
// mod id, which means our permission nodes end up becoming
// geyser_neoforge.<node> instead of just <node>. We work around
// this by using reflection to access the constructor that
// doesn't require a mod id or ResourceLocation.
try {
return (PermissionNode<Boolean>) PERMISSION_NODE_CONSTRUCTOR.newInstance(
node,
PermissionTypes.BOOLEAN,
(PermissionNode.PermissionResolver<Boolean>) (player, playerUUID, context) -> false,
new PermissionDynamicContextKey[0]
);
} catch (Exception e) {
throw new RuntimeException("Unable to create permission node " + node, e);
}
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 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.neoforge;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.platform.neoforge.mixin.PermissionNodeMixin;
/**
* Common logic for handling the more complicated way we have to register permission on NeoForge
*/
public class PermissionUtils {
private PermissionUtils() {
//no
}
/**
* Registers the given permission and its default value to the event. If the permission has the same name as one
* that has already been registered to the event, it will not be registered. In other words, it will not override.
*
* @param permission the permission to register
* @param permissionDefault the permission's default value. See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings.
* @param event the registration event
* @return true if the permission was registered
*/
public static boolean register(String permission, TriState permissionDefault, PermissionGatherEvent.Nodes event) {
// NeoForge likes to crash if you try and register a duplicate node
if (event.getNodes().stream().noneMatch(n -> n.getNodeName().equals(permission))) {
PermissionNode<Boolean> node = createNode(permission, permissionDefault);
event.addNodes(node);
return true;
}
return false;
}
private static PermissionNode<Boolean> createNode(String node, TriState permissionDefault) {
return PermissionNodeMixin.geyser$construct(
node,
PermissionTypes.BOOLEAN,
(player, playerUUID, context) -> switch (permissionDefault) {
case TRUE -> true;
case FALSE -> false;
case NOT_SET -> {
if (player != null) {
yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel());
}
yield false; // NeoForge javadocs say player is null in the case of an offline player.
}
}
);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2024 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.neoforge.mixin;
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(value = PermissionNode.class, remap = false) // this is API - do not remap
public interface PermissionNodeMixin {
/**
* Invokes the matching private constructor in {@link PermissionNode}.
* <p>
* The typical constructors in PermissionNode require a mod id, which means our permission nodes
* would end up becoming {@code geyser_neoforge.<node>} instead of just {@code <node>}.
*/
@SuppressWarnings("rawtypes") // the varargs
@Invoker("<init>")
static <T> PermissionNode<T> geyser$construct(String nodeName, PermissionType<T> type, PermissionNode.PermissionResolver<T> defaultResolver, PermissionDynamicContextKey... dynamics) {
throw new IllegalStateException();
}
}

View file

@ -11,6 +11,8 @@ authors="GeyserMC"
description="${description}"
[[mixins]]
config = "geyser.mixins.json"
[[mixins]]
config = "geyser_neoforge.mixins.json"
[[dependencies.geyser_neoforge]]
modId="neoforge"
type="required"

View file

@ -0,0 +1,12 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.geysermc.geyser.platform.neoforge.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"PermissionNodeMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -25,30 +25,21 @@
package org.geysermc.geyser.platform.mod;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
@ -59,7 +50,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
@ -70,13 +60,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
private final GeyserModPlatform platform;
@Getter
private GeyserImpl geyser;
private Path dataFolder;
@Setter
@Setter @Getter
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
@Setter
private CommandRegistry commandRegistry;
private GeyserModConfiguration geyserConfig;
private GeyserModInjector geyserInjector;
private final GeyserModLogger geyserLogger = new GeyserModLogger();
@ -94,10 +86,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
// Create command manager here, since the permission handler on neo needs it
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
public void onGeyserEnable() {
@ -130,50 +118,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
if (isServer()) {
this.geyserInjector.initializeLocalChannel(this);
}
// Start command building
// Set just "geyser" as the help command
GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
.requires(executor::testPermission)
// Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(builder);
// Register extension commands
for (Map.Entry<Extension, Map<String, Command>> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
Map<String, Command> extensionCommands = extensionMapEntry.getValue();
if (extensionCommands.isEmpty()) {
continue;
}
// Register help command for just "/<extensionId>"
GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor)
.requires(executor::testPermission)
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(extCmdBuilder);
}
}
@Override
@ -206,8 +150,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return commandRegistry;
}
@Override
@ -235,6 +179,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.server.getServerVersion();
}
@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
@NonNull
@Override
public String getServerBindAddress() {
@ -270,10 +215,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.platform.resolveResource(resource);
}
public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {

View file

@ -25,17 +25,18 @@
package org.geysermc.geyser.platform.mod;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.platform.mod.command.ModCommandSender;
import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener {
public static void onPlayReady(Player player) {
CommandSourceStack stack = player.createCommandSourceStack();
if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
// Should be creating this in the supplier, but we need it for the permission check.
// Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
if (source.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> source);
}
}

View file

@ -1,75 +0,0 @@
/*
* 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.mod.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
private final GeyserCommand command;
public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
super(geyser, Collections.singletonMap(command.name(), command));
this.command = command;
}
public boolean testPermission(CommandSourceStack source) {
return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
return runWithArgs(context, "");
}
public int runWithArgs(CommandContext<CommandSourceStack> context, String args) {
CommandSourceStack source = context.getSource();
ModCommandSender sender = new ModCommandSender(source);
GeyserSession session = getGeyserSession(sender);
if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0;
}
if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
return 0;
}
command.execute(session, sender, args.split(" "));
return 0;
}
}

View file

@ -31,19 +31,21 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor;
import java.util.Objects;
import java.util.UUID;
public class ModCommandSender implements GeyserCommandSource {
public class ModCommandSource implements GeyserCommandSource {
private final CommandSourceStack source;
public ModCommandSender(CommandSourceStack source) {
public ModCommandSource(CommandSourceStack source) {
this.source = source;
// todo find locale?
}
@Override
@ -75,8 +77,24 @@ public class ModCommandSender implements GeyserCommandSource {
return !(source.getEntity() instanceof ServerPlayer);
}
@Override
public @Nullable UUID playerUuid() {
if (source.getEntity() instanceof ServerPlayer player) {
return player.getUUID();
}
return null;
}
@Override
public boolean hasPermission(String permission) {
return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
// Unlike other bootstraps; we delegate to cloud here too:
// On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
// For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
}
@Override
public Object handle() {
return source;
}
}

View file

@ -48,7 +48,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
@ -111,12 +110,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
ServerPlayer player = getPlayer(session);
return GeyserModBootstrap.getInstance().hasPermission(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());