Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 62 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,122 +1,95 @@
# Sniffer
> [!NOTE]
> This project is now being developed by Bookshelf team and has been renamed to Sniffer. Bookshelf has added more features to Sniffer and developed a VSCode plugin! So head over to their repository to stay updated~
>
> It's worth mentioning that the original author is still actively contributing to Sniffer's development~
>
> Sniffer: <https://github.com/mcbookshelf/sniffer>

## Overview

Sniffer is a debug adapter for Minecraft datapacks that allows you to debug your `.mcfunction` files directly from Visual Studio Code. It provides features like breakpoints, step execution, and variable inspection to make datapack development easier and more efficient.
# Datapack Breakpoint

## Features
English | [简体中文](README_zh.md)

- Set breakpoints in `.mcfunction` files
- Connect to a running Minecraft instance
- Inspect game state during debugging
- Step through command execution
- Path mapping between Minecraft and local files
## Introduce

## Requirements
This is a fabric mod for Minecraft 1.21, which allows you to set breakpoints in the game and "freeze" the game when
the breakpoint is reached.

- Minecraft with Fabric Loader
- Visual Studio Code
## Usage

* Set a breakpoint

<!-- ## Installation
In datapack, you can insert `#breakpoint` into .mcfunction file to set a breakpoint. For example:

### Minecraft Mod Installation
```mcfunction
#test:test

1. Install [Fabric Loader](https://fabricmc.net/use/) for your Minecraft version
2. Download the Sniffer mod JAR from the [releases page](https://github.com/mcbookshelf/sniffer/releases)
3. Place the JAR file in your Minecraft `mods` folder
4. Launch Minecraft with Fabric

### VSCode Extension Installation

1. Open Visual Studio Code
2. Go to the Extensions view (Ctrl+Shift+X)
3. Search for "Sniffer"
4. Click Install -->

## Mod Configuration
The mod can be configured through the in-game configuration screen, accessible via Mod Menu.
You can also configure the mod in the `config/sniffer.json` file.
The following options are available:

### Debug Server Settings
- **Server Port**: The port number for the debug server (default: 25599)
- **Server path**: The path to the debug server (default: `/dap`)

## Connecting to Minecraft

1. Open your datapack project in VSCode
2. Create a `.vscode/launch.json` file with the following configuration:

```json
{
"version": "0.2.0",
"configurations": [
{
"type": "sniffer",
"request": "attach",
"name": "Connect to Minecraft",
"address": "ws://localhost:25599/dap"
}
]
}
say 1
say 2
#breakpoint
say 3
say 4
```

3. Start Minecraft with the Sniffer mod installed
4. In VSCode, press F5 or click the "Run and Debug" button
5. Select "Connect to Minecraft" from the dropdown menu
In this case, after the game executes `say 2`, the game will be "frozen" because it meets the breakpoint.

You can now place breakpoints in your `.mcfunction` files and execute it from the game to step through the code.
When the game is "frozen", you can still move around, do whatever you want, just like execute the command `tick freeze`.
So you can check the game state, or do some debugging.

## Usage in Minecraft
* Step

The debugger can be controlled directly from Minecraft using the following commands:
When the game is "frozen", you can use the command `/breakpoint step` to execute the next command. In above example,
after the game meets the breakpoint, you can use `/breakpoint step` to execute `say 3`, and then use `/breakpoint step`
to execute `say 4`. When all commands are executed, the game will be unfrozen and continue running.

- `/breakpoint continue`: Resume execution after hitting a breakpoint
- `/breakpoint step`: Execute the next command and pause
- `/breakpoint step_over`: Skip to the next command in the current function
- `/breakpoint step_out`: Continue execution until the current function returns
* Continue

All commands require operator permissions (level 2) to use.
When the game is "frozen", you can use the command `/breakpoint move` to unfreeze the game and continue running.

When execution is paused at a breakpoint, the gametick will be freezed.
* Get Macro Arguments

By using `/breakpoint get <key>`, you can get the value of the macro argument if the game is executing a macro function.
For example:

```mcfunction
#test:test_macro

## Development

### Project Structure
say start
#breakpoint
$say $(msg)
say end
```

- `src/main`: Main mod code for Minecraft
- `src/client`: Client-side mod code
- `vscode`: VSCode extension source code
After executing `function test:test_macro {"msg":"test"}`, we passed the value `test` to the macro argument `msg` and
then the game will pause before `$say $(msg)`. At this time, you can use `/breakpoint get msg` to get the value `test`.

### Building the Project
* Get Function Stack

To build the Minecraft mod:
By using `/breakpoint stack`, you can get the function stack of the current game. For example, if we have following two
functions:

```bash
./gradlew build
```
```mcfunction
#test:test1

To build the VSCode extension:
say 1
function test:test2
say 2

```bash
cd vscode
npm install
npm run build
#test: test2
say A
#breakpoint
say B
```

## License
When the game pauses at the breakpoint, you can use `/breakpoint stack` and the function stack will be printed in the
chat screen:

This project is licensed under the MPL-2.0 License - see the [LICENSE](LICENSE) file for details.

## Contributing
```
test:test2
test:test

Contributions are welcome! Please feel free to submit a Pull Request.
```

## Acknowledgements
* Run command in current context

- [Fabric](https://fabricmc.net/) - Mod loader for Minecraft
- [VSCode Debug Adapter](https://code.visualstudio.com/api/extension-guides/debugger-extension) - VSCode debugging API
- [Datapack Debugger](https://github.com/Alumopper/Datapack-Debugger/) by [Alumopper](https://github.com/Alumopper) - Original implementation of the debugger, without the DAP layer
By using `/breakpoint run <command>`, you can run any command in the current context, just like `execute ... run ...`.
8 changes: 5 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '1.7-SNAPSHOT'
id 'fabric-loom' version '1.11-SNAPSHOT'
id 'maven-publish'
}

Expand Down Expand Up @@ -32,6 +32,8 @@ loom {
}
}

// log4jConfigs.from(file("log4j.xml"))

}

dependencies {
Expand All @@ -51,10 +53,10 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-key-binding-api-v1:${project.fabric_version}"

// Added Cloth Config and ModMenu
modApi("me.shedaniel.cloth:cloth-config-fabric:17.0.144") {
modApi("me.shedaniel.cloth:cloth-config-fabric:20.0.148") {
exclude(group: "net.fabricmc.fabric-api")
}
modImplementation("com.terraformersmc:modmenu:13.0.2")
modImplementation("com.terraformersmc:modmenu:16.0.0-rc.1")
}

processResources {
Expand Down
9 changes: 5 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ org.gradle.parallel=true

# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.10
minecraft_version=1.21.10
yarn_mappings=1.21.10+build.2
loader_version=0.17.3
loom_version=1.11-SNAPSHOT

# Mod Properties
mod_version=0.1.0
maven_group=net.gunivers
archives_base_name=sniffer

# Dependencies
fabric_version=0.118.0+1.21.4
fabric_version=0.136.0+1.21.10
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://maven.fastmirror.net/repositories/gradle-dist/gradle-8.8-bin.zip
distributionUrl=https\://maven.fastmirror.net/repositories/gradle-dist/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
import net.gunivers.sniffer.command.BreakPointCommand;;

Expand All @@ -22,13 +23,13 @@ public void onInitializeClient() {
"sniffer.step", // The translation key of the keybinding's name
InputUtil.Type.KEYSYM, // The type of the keybinding, KEYSYM for keyboard, MOUSE for mouse.
GLFW.GLFW_KEY_F7, // The keycode of the key
"sniffer.name" // The translation key of the keybinding's category.
KeyBinding.Category.create(Identifier.of("sniffer.name")) // The translation key of the keybinding's category.
));

ClientTickEvents.END_CLIENT_TICK.register(client -> {
while(stepInto.wasPressed()) {
if(BreakPointCommand.debugMode) {
BreakPointCommand.step(1, client.player.getCommandSource(client.getServer().getWorld(client.player.getWorld().getRegistryKey())));
BreakPointCommand.step(1, client.player.getCommandSource(client.getServer().getWorld(client.player.getEntityWorld().getRegistryKey())));
}
}
});
Expand Down
80 changes: 36 additions & 44 deletions src/main/java/net/gunivers/sniffer/command/BreakPointCommand.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
package net.gunivers.sniffer.command;

import com.google.common.collect.Queues;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.gunivers.sniffer.DatapackDebugger;
import net.gunivers.sniffer.dap.DebuggerState;
import net.gunivers.sniffer.dap.ScopeManager;
import net.gunivers.sniffer.util.ReflectUtil;
import net.minecraft.command.CommandExecutionContext;

import static net.gunivers.sniffer.Utils.addSnifferPrefix;
import static net.minecraft.server.command.CommandManager.literal;
import static net.minecraft.server.command.CommandManager.argument;

import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import net.minecraft.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.gunivers.sniffer.DatapackDebugger;
import net.gunivers.sniffer.dap.DebuggerState;
import net.gunivers.sniffer.dap.ScopeManager;

import java.util.Deque;

import static net.gunivers.sniffer.util.Utils.addSnifferPrefix;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;

/**
* Main command handler for the datapack debugging system.
* Provides commands for setting breakpoints, stepping through code, and inspecting variables.
Expand Down Expand Up @@ -173,7 +175,7 @@ public static void onInitialize() {
var t = Text.literal(stack.getFunction());
var style = t.getStyle();
if(stacks.indexOf(stack) == 0){
style = style.withBold(true);
style = style.withBold(true).withColor(TextColor.parse("aqua").getOrThrow());
}else {
style = style.withBold(false);
}
Expand Down Expand Up @@ -240,40 +242,30 @@ public static void step(int steps, ServerCommandSource source) {
isDebugCommand = true;
moveSteps = steps;
CommandExecutionContext<?> context = null;
try {
while (moveSteps > 0) {
context = storedCommandExecutionContext.peekFirst();
if (context != null) {
var cls = context.getClass();
var method = cls.getDeclaredMethod("onStep");
method.setAccessible(true);
method.invoke(context);
if (moveSteps != 0) {
while (moveSteps > 0) {
context = storedCommandExecutionContext.peekFirst();
if (context != null) {
ReflectUtil.invoke(context, "onStep").onFailure(LOGGER::error);
if (moveSteps != 0) {
storedCommandExecutionContext.pollFirst().close();
}else {
var result = (boolean) ReflectUtil.invoke(context, "ifContainsCommandAction").onFailure(LOGGER::error).getData();
if(!result){
storedCommandExecutionContext.pollFirst().close();
}else {
var method1 = cls.getDeclaredMethod("ifContainsCommandAction");
method1.setAccessible(true);
boolean result = (boolean) method1.invoke(context);
if(!result){
storedCommandExecutionContext.pollFirst().close();
}
break;
}
} else {
source.sendFeedback(() -> addSnifferPrefix(Text.translatable("sniffer.commands.breakpoint.step.over").formatted(Formatting.WHITE)), false);
continueExec(source);
break;
}
} else {
source.sendFeedback(() -> addSnifferPrefix(Text.translatable("sniffer.commands.breakpoint.step.over").formatted(Formatting.WHITE)), false);
continueExec(source);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
isDebugCommand = false;
if (context != null) {
try {
context.close();
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
}
isDebugCommand = false;
if (context != null) {
try {
context.close();
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
}
}
Expand Down Expand Up @@ -313,10 +305,10 @@ public static void continueExec(@NotNull ServerCommandSource source) {
return null;
}
try {
var cls = context.getClass();
var method = cls.getDeclaredMethod("getKey", String.class);
method.setAccessible(true);
return (Pair<NbtElement, Boolean>) method.invoke(context, key);
//noinspection unchecked
return (Pair<NbtElement, Boolean>) ReflectUtil.invoke(context, "getKey", key)
.onFailure(LOGGER::error)
.getDataOrElse(null);
}catch (Exception e){
LOGGER.error(e.toString());
source.sendError(Text.translatable("sniffer.commands.breakpoint.get.fail.error", e.toString()));
Expand Down
Loading