feat: enhance decompilation and server management scripts

- Introduced `decompile.sh` to automate the decompilation process, including downloading `MinecraftDecompiler.jar` and server.jar for specified Minecraft versions.
- Added `mc-rcon.sh` for sending RCON commands to a Minecraft server, improving server management capabilities.
- Created `mcc-env.sh` to provide helper functions for managing Minecraft servers, including starting, stopping, and sending commands.
- Implemented `start-server.sh` to launch a Minecraft server in a tmux session with a named pipe for stdin, facilitating easier command input.
- Updated `README.md` to reflect new tools and usage instructions for decompiling and server management.

These changes streamline the workflow for adapting to new Minecraft versions and enhance the overall development experience.
This commit is contained in:
BruceChen 2026-03-22 15:23:55 +08:00
parent 9e3bfaa868
commit cca4134e8b
6 changed files with 301 additions and 11 deletions

View file

@ -18,17 +18,21 @@ Two types of data can be used as input:
### Decompiling a new MC version
```bash
cd MinecraftOfficial
java -jar MinecraftDecompiler.jar --version 1.21.9 --side SERVER \
--decompile --output 1.21.9-remapped.jar --decompiled-output 1.21.9-decompiled
# Server side (default) — also downloads server.jar into MinecraftOfficial/downloads/<ver>/
tools/decompile.sh --version 1.21.9
# Client side
tools/decompile.sh --version 1.21.9 --side CLIENT
```
The script auto-downloads `MinecraftDecompiler.jar` from GitHub releases if it doesn't exist.
### Generating server data reports
```bash
cd /tmp
java -DbundlerMainClass=net.minecraft.data.Main \
-jar /path/to/server.jar \
-jar $MCC_SERVERS/<version>/server.jar \
--reports --output /tmp/mc_reports
```

138
tools/decompile.sh Normal file
View file

@ -0,0 +1,138 @@
#!/bin/bash
# Download (if needed) and run MinecraftDecompiler to produce decompiled source
# and server.jar for a given Minecraft version.
#
# Usage:
# ./tools/decompile.sh --version <ver> [--side SERVER|CLIENT]
#
# Examples:
# ./tools/decompile.sh --version 1.21.11
# ./tools/decompile.sh --version 1.21.11 --side CLIENT
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MC_OFFICIAL="$REPO_ROOT/MinecraftOfficial"
DECOMPILER_JAR="$MC_OFFICIAL/MinecraftDecompiler.jar"
DECOMPILER_REPO="MaxPixelStudios/MinecraftDecompiler"
VERSION=""
SIDE="SERVER"
while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="$2"; shift 2 ;;
--side) SIDE="$(echo "$2" | tr '[:lower:]' '[:upper:]')"; shift 2 ;;
-h|--help)
echo "Usage: $0 --version <ver> [--side SERVER|CLIENT]"
echo ""
echo "Options:"
echo " --version <ver> Minecraft version (e.g. 1.21.11)"
echo " --side <env> SERVER (default) or CLIENT"
exit 0
;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$VERSION" ]]; then
echo "Error: --version is required"
echo "Usage: $0 --version <ver> [--side SERVER|CLIENT]"
exit 1
fi
if [[ "$SIDE" != "SERVER" && "$SIDE" != "CLIENT" ]]; then
echo "Error: --side must be SERVER or CLIENT (got: $SIDE)"
exit 1
fi
# --- Ensure MinecraftDecompiler.jar exists ---
if [[ ! -f "$DECOMPILER_JAR" ]]; then
echo "MinecraftDecompiler.jar not found, downloading latest release..."
DOWNLOAD_URL=$(curl -sL "https://api.github.com/repos/$DECOMPILER_REPO/releases/latest" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
for a in data['assets']:
if a['name'] == 'MinecraftDecompiler.jar':
print(a['browser_download_url'])
break
")
if [[ -z "$DOWNLOAD_URL" ]]; then
echo "Error: could not find MinecraftDecompiler.jar in latest release"
exit 1
fi
echo "Downloading from $DOWNLOAD_URL ..."
curl -L -o "$DECOMPILER_JAR" "$DOWNLOAD_URL"
echo "Downloaded MinecraftDecompiler.jar"
fi
# --- Build output paths ---
SIDE_LOWER="$(echo "$SIDE" | tr '[:upper:]' '[:lower:]')"
if [[ "$SIDE" == "SERVER" ]]; then
REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-remapped.jar"
DECOMPILED_DIR="$MC_OFFICIAL/${VERSION}-decompiled"
else
REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-${SIDE_LOWER}-remapped.jar"
DECOMPILED_DIR="$MC_OFFICIAL/${VERSION}-${SIDE_LOWER}-decompiled"
fi
if [[ -d "$DECOMPILED_DIR" ]]; then
echo "Decompiled source already exists: $DECOMPILED_DIR"
echo "Delete it first if you want to re-decompile."
exit 0
fi
mkdir -p "$MC_OFFICIAL/remapped_jar"
echo "=== Decompiling Minecraft $VERSION ($SIDE) ==="
echo " Remapped JAR: $REMAPPED_JAR"
echo " Decompiled: $DECOMPILED_DIR"
echo ""
cd "$MC_OFFICIAL"
java -jar "$DECOMPILER_JAR" \
--version "$VERSION" \
--side "$SIDE" \
--decompile \
--output "$REMAPPED_JAR" \
--decompiled-output "$DECOMPILED_DIR"
echo ""
echo "=== Done ==="
echo "Decompiled source: $DECOMPILED_DIR"
# --- For SERVER side, also ensure downloads/<ver>/server.jar exists ---
if [[ "$SIDE" == "SERVER" ]]; then
DOWNLOADS_DIR="$MC_OFFICIAL/downloads/$VERSION"
if [[ ! -f "$DOWNLOADS_DIR/server.jar" ]]; then
mkdir -p "$DOWNLOADS_DIR"
# MinecraftDecompiler downloads the original jar into its cache;
# extract it from the bundled remapped jar or re-download via manifest.
echo ""
echo "Downloading server.jar for $VERSION into $DOWNLOADS_DIR ..."
MANIFEST_URL="https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"
VERSION_URL=$(curl -sL "$MANIFEST_URL" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for v in data['versions']:
if v['id'] == '$VERSION':
print(v['url'])
break
")
if [[ -n "$VERSION_URL" ]]; then
SERVER_JAR_URL=$(curl -sL "$VERSION_URL" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data['downloads']['server']['url'])
")
curl -L -o "$DOWNLOADS_DIR/server.jar" "$SERVER_JAR_URL"
echo "Downloaded server.jar"
else
echo "Warning: could not find version $VERSION in Mojang manifest; server.jar not downloaded."
fi
else
echo "server.jar already exists: $DOWNLOADS_DIR/server.jar"
fi
fi

46
tools/mc-rcon.sh Normal file
View file

@ -0,0 +1,46 @@
#!/bin/bash
# Send an RCON command to a Minecraft server
# Usage: mc-rcon.sh <command> [port] [password]
set -euo pipefail
CMD="${1:?Usage: mc-rcon.sh <command> [port] [password]}"
PORT="${2:-25575}"
PW="${3:-test123}"
python3 -c "
import socket, struct, sys
s = socket.socket()
s.settimeout(5)
try:
s.connect(('localhost', $PORT))
except Exception as e:
print(f'Connection failed: {e}', file=sys.stderr)
sys.exit(1)
def send(req_id, pkt_type, body):
body = body.encode()
s.send(struct.pack('<iii', 10 + len(body), req_id, pkt_type) + body + b'\x00\x00')
def recv():
length = struct.unpack('<i', s.recv(4))[0]
data = b''
while len(data) < length:
data += s.recv(length - len(data))
return data
send(1, 3, '$PW')
r = recv()
rid = struct.unpack('<i', r[:4])[0]
if rid == -1:
print('Auth failed', file=sys.stderr)
s.close()
sys.exit(1)
send(2, 2, \"\"\"$CMD\"\"\")
r = recv()
body = r[8:-2].decode(errors='replace')
if body:
print(body)
s.close()
"

33
tools/mcc-env.sh Normal file
View file

@ -0,0 +1,33 @@
#!/bin/bash
# MCC (Minecraft Console Client) Development Utilities
# Source this file to get helper functions: source $MCC_REPO/tools/mcc-env.sh
# Or add to ~/.bashrc: source "$HOME/Minecraft/Minecraft-Console-Client-milutinke/tools/mcc-env.sh"
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export MCC_REPO="$(cd "$TOOLS_DIR/.." && pwd)"
export MCC_SERVERS="$MCC_REPO/MinecraftOfficial/downloads"
# Helper: convert version to tmux session name (dots -> underscores)
_mc-session() { echo "mc-${1//\./_}"; }
# --- Minecraft Server Management ---
mc-start() { "$MCC_REPO/tools/start-server.sh" "${1:-1.20.6}"; }
mc-stop() { local v="${1:-1.20.6}"; echo "stop" > "$MCC_SERVERS/$v/stdin.pipe"; }
mc-cmd() { local v="${2:-1.20.6}"; echo "$1" > "$MCC_SERVERS/$v/stdin.pipe"; }
mc-log() { local s; s=$(_mc-session "${1:-1.20.6}"); tmux capture-pane -t "$s" -p -S "-${2:-50}"; }
mc-kill() { local v="${1:-1.20.6}" s; s=$(_mc-session "$v"); tmux kill-session -t "$s" 2>/dev/null; rm -f "$MCC_SERVERS/$v/stdin.pipe"; echo "Killed $s"; }
mc-list() { tmux list-sessions 2>/dev/null | grep "^mc-" || echo "No running MC servers"; }
# --- RCON ---
mc-rcon() { "$MCC_REPO/tools/mc-rcon.sh" "$@"; }
# --- MCC Build/Run ---
mcc-build() { dotnet build "$MCC_REPO/MinecraftClient.sln" -c Release; }
mcc-run() { cd "$MCC_REPO" && MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release -- CursorBot - "localhost:${1:-25565}" 2>&1; }
mcc-cmd() { echo "$1" >> "$MCC_REPO/mcc_input.txt"; }
mcc-kill() { pkill -f "MinecraftClient" 2>/dev/null && echo "MCC killed" || echo "No MCC process found"; }
mcc-reload() {
mcc-kill
sleep 1
mcc-build && mcc-run
}

39
tools/start-server.sh Normal file
View file

@ -0,0 +1,39 @@
#!/bin/bash
# Start a Minecraft server in a tmux session with named pipe for stdin
# Servers live in MinecraftOfficial/downloads/<version>/ alongside the downloaded server.jar
VERSION="${1}"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DOWNLOADS="$REPO_ROOT/MinecraftOfficial/downloads"
DIR="$DOWNLOADS/$VERSION"
PIPE="$DIR/stdin.pipe"
SESSION="mc-${VERSION//\./_}"
if [ -z "$VERSION" ] || [ ! -d "$DIR" ]; then
echo "Error: Server directory not found${VERSION:+: $DIR}"
echo "Available versions:"
ls "$DOWNLOADS" | grep -E '^[0-9]' | sort -V
exit 1
fi
if [ ! -f "$DIR/server.jar" ]; then
echo "Error: No server.jar in $DIR"
exit 1
fi
if tmux has-session -t "$SESSION" 2>/dev/null; then
echo "Server $VERSION already running in tmux session '$SESSION'"
echo "View output: tmux capture-pane -t '$SESSION' -p -S -50"
echo "Send command: echo 'say hello' > $PIPE"
exit 0
fi
rm -f "$DIR/world/session.lock"
[ -p "$PIPE" ] || mkfifo "$PIPE"
tmux new-session -d -s "$SESSION" -c "$DIR" \
"tail -f $PIPE | java -Xmx2G -Xms2G -jar server.jar nogui 2>&1"
echo "Server $VERSION started in tmux session '$SESSION'"
echo "Send commands: echo 'say hello' > $PIPE"
echo "View output: tmux capture-pane -t '$SESSION' -p -S -50"