package net.minecraft.server.commands; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ContextChain; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Locale; import net.minecraft.Util; import net.minecraft.commands.CommandResultCallback; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.FunctionInstantiationException; import net.minecraft.commands.arguments.item.FunctionArgument; import net.minecraft.commands.execution.ChainModifiers; import net.minecraft.commands.execution.CustomCommandExecutor; import net.minecraft.commands.execution.ExecutionControl; import net.minecraft.commands.execution.TraceCallbacks; import net.minecraft.commands.functions.CommandFunction; import net.minecraft.commands.functions.InstantiatedFunction; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.commands.DebugCommand.TraceCustomExecutor.1; import net.minecraft.util.TimeUtil; import net.minecraft.util.profiling.ProfileResults; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; public class DebugCommand { static final Logger LOGGER = LogUtils.getLogger(); private static final SimpleCommandExceptionType ERROR_NOT_RUNNING = new SimpleCommandExceptionType(Component.translatable("commands.debug.notRunning")); private static final SimpleCommandExceptionType ERROR_ALREADY_RUNNING = new SimpleCommandExceptionType(Component.translatable("commands.debug.alreadyRunning")); static final SimpleCommandExceptionType NO_RECURSIVE_TRACES = new SimpleCommandExceptionType(Component.translatable("commands.debug.function.noRecursion")); static final SimpleCommandExceptionType NO_RETURN_RUN = new SimpleCommandExceptionType(Component.translatable("commands.debug.function.noReturnRun")); public static void register(CommandDispatcher dispatcher) { dispatcher.register( Commands.literal("debug") .requires(commandSourceStack -> commandSourceStack.hasPermission(3)) .then(Commands.literal("start").executes(commandContext -> start(commandContext.getSource()))) .then(Commands.literal("stop").executes(commandContext -> stop(commandContext.getSource()))) .then( Commands.literal("function") .requires(commandSourceStack -> commandSourceStack.hasPermission(3)) .then(Commands.argument("name", FunctionArgument.functions()).suggests(FunctionCommand.SUGGEST_FUNCTION).executes(new DebugCommand.TraceCustomExecutor())) ) ); } private static int start(CommandSourceStack source) throws CommandSyntaxException { MinecraftServer minecraftServer = source.getServer(); if (minecraftServer.isTimeProfilerRunning()) { throw ERROR_ALREADY_RUNNING.create(); } else { minecraftServer.startTimeProfiler(); source.sendSuccess(() -> Component.translatable("commands.debug.started"), true); return 0; } } private static int stop(CommandSourceStack source) throws CommandSyntaxException { MinecraftServer minecraftServer = source.getServer(); if (!minecraftServer.isTimeProfilerRunning()) { throw ERROR_NOT_RUNNING.create(); } else { ProfileResults profileResults = minecraftServer.stopTimeProfiler(); double d = (double)profileResults.getNanoDuration() / TimeUtil.NANOSECONDS_PER_SECOND; double e = profileResults.getTickDuration() / d; source.sendSuccess( () -> Component.translatable( "commands.debug.stopped", String.format(Locale.ROOT, "%.2f", d), profileResults.getTickDuration(), String.format(Locale.ROOT, "%.2f", e) ), true ); return (int)e; } } static class TraceCustomExecutor extends CustomCommandExecutor.WithErrorHandling implements CustomCommandExecutor.CommandAdapter { public void runGuarded( CommandSourceStack commandSourceStack, ContextChain contextChain, ChainModifiers chainModifiers, ExecutionControl executionControl ) throws CommandSyntaxException { if (chainModifiers.isReturn()) { throw DebugCommand.NO_RETURN_RUN.create(); } else if (executionControl.tracer() != null) { throw DebugCommand.NO_RECURSIVE_TRACES.create(); } else { CommandContext commandContext = contextChain.getTopContext(); Collection> collection = FunctionArgument.getFunctions(commandContext, "name"); MinecraftServer minecraftServer = commandSourceStack.getServer(); String string = "debug-trace-" + Util.getFilenameFormattedDateTime() + ".txt"; CommandDispatcher commandDispatcher = commandSourceStack.getServer().getFunctions().getDispatcher(); int i = 0; try { Path path = minecraftServer.getFile("debug"); Files.createDirectories(path); PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(path.resolve(string), StandardCharsets.UTF_8)); DebugCommand.Tracer tracer = new DebugCommand.Tracer(printWriter); executionControl.tracer(tracer); for (CommandFunction commandFunction : collection) { try { CommandSourceStack commandSourceStack2 = commandSourceStack.withSource(tracer).withMaximumPermission(2); InstantiatedFunction instantiatedFunction = commandFunction.instantiate(null, commandDispatcher); executionControl.queueNext(new 1(this, instantiatedFunction, CommandResultCallback.EMPTY, false, printWriter, commandFunction).bind(commandSourceStack2)); i += instantiatedFunction.entries().size(); } catch (FunctionInstantiationException var18) { commandSourceStack.sendFailure(var18.messageComponent()); } } } catch (IOException | UncheckedIOException var19) { DebugCommand.LOGGER.warn("Tracing failed", (Throwable)var19); commandSourceStack.sendFailure(Component.translatable("commands.debug.function.traceFailed")); } int j = i; executionControl.queueNext( (executionContext, frame) -> { if (collection.size() == 1) { commandSourceStack.sendSuccess( () -> Component.translatable( "commands.debug.function.success.single", j, Component.translationArg(((CommandFunction)collection.iterator().next()).id()), string ), true ); } else { commandSourceStack.sendSuccess(() -> Component.translatable("commands.debug.function.success.multiple", j, collection.size(), string), true); } } ); } } } static class Tracer implements CommandSource, TraceCallbacks { public static final int INDENT_OFFSET = 1; private final PrintWriter output; private int lastIndent; private boolean waitingForResult; Tracer(PrintWriter output) { this.output = output; } private void indentAndSave(int indent) { this.printIndent(indent); this.lastIndent = indent; } private void printIndent(int indent) { for (int i = 0; i < indent + 1; i++) { this.output.write(" "); } } private void newLine() { if (this.waitingForResult) { this.output.println(); this.waitingForResult = false; } } @Override public void onCommand(int depth, String command) { this.newLine(); this.indentAndSave(depth); this.output.print("[C] "); this.output.print(command); this.waitingForResult = true; } @Override public void onReturn(int depth, String command, int returnValue) { if (this.waitingForResult) { this.output.print(" -> "); this.output.println(returnValue); this.waitingForResult = false; } else { this.indentAndSave(depth); this.output.print("[R = "); this.output.print(returnValue); this.output.print("] "); this.output.println(command); } } @Override public void onCall(int depth, ResourceLocation function, int commands) { this.newLine(); this.indentAndSave(depth); this.output.print("[F] "); this.output.print(function); this.output.print(" size="); this.output.println(commands); } @Override public void onError(String errorMessage) { this.newLine(); this.indentAndSave(this.lastIndent + 1); this.output.print("[E] "); this.output.print(errorMessage); } @Override public void sendSystemMessage(Component component) { this.newLine(); this.printIndent(this.lastIndent + 1); this.output.print("[M] "); this.output.println(component.getString()); } @Override public boolean acceptsSuccess() { return true; } @Override public boolean acceptsFailure() { return true; } @Override public boolean shouldInformAdmins() { return false; } @Override public boolean alwaysAccepts() { return true; } @Override public void close() { IOUtils.closeQuietly(this.output); } } }