#!/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 [--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 [--side SERVER|CLIENT]" echo "" echo "Options:" echo " --version Minecraft version (e.g. 1.21.11)" echo " --side 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 [--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" # --- Resolve version metadata from Mojang manifest --- 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 [[ -z "$VERSION_URL" ]]; then echo "Error: version $VERSION not found in Mojang launcher manifest." exit 1 fi VERSION_META=$(curl -sL "$VERSION_URL") MAPPING_KEY="${SIDE_LOWER}_mappings" HAS_MAPPINGS=$(echo "$VERSION_META" | python3 -c " import json, sys data = json.load(sys.stdin) print('true' if '$MAPPING_KEY' in data.get('downloads', {}) else 'false') ") echo "=== Decompiling Minecraft $VERSION ($SIDE) ===" echo " Remapped JAR: $REMAPPED_JAR" echo " Decompiled: $DECOMPILED_DIR" echo " Obfuscated: $HAS_MAPPINGS" echo "" cd "$MC_OFFICIAL" if [[ "$HAS_MAPPINGS" == "true" ]]; then # Obfuscated version: use --version/--side to auto-download jar + mappings + deobfuscate java -jar "$DECOMPILER_JAR" \ --version "$VERSION" \ --side "$SIDE" \ --decompile \ --output "$REMAPPED_JAR" \ --decompiled-output "$DECOMPILED_DIR" else # Unobfuscated version (26.1+): download jar, extract inner jar from bundle, decompile directly. # MinecraftDecompiler requires --mapping-path with --input, but unobfuscated versions # have no mappings. We use Vineflower directly instead. echo "No Proguard mappings for $VERSION; decompiling without deobfuscation." JAR_URL=$(echo "$VERSION_META" | python3 -c " import json, sys data = json.load(sys.stdin) print(data['downloads']['${SIDE_LOWER}']['url']) ") ORIGINAL_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-${SIDE_LOWER}-original.jar" if [[ ! -f "$ORIGINAL_JAR" ]]; then echo "Downloading ${SIDE_LOWER}.jar ..." curl -L -o "$ORIGINAL_JAR" "$JAR_URL" fi # Since 1.18, server.jar is a bundled jar containing the actual game jar inside # META-INF/versions//server-.jar. Extract it if present. DECOMPILE_TARGET="$ORIGINAL_JAR" EXTRACT_DIR=$(mktemp -d) trap "rm -rf '$EXTRACT_DIR'" EXIT if unzip -q -o "$ORIGINAL_JAR" "META-INF/versions.list" -d "$EXTRACT_DIR" 2>/dev/null; then INNER_PATH=$(awk '{print $NF}' "$EXTRACT_DIR/META-INF/versions.list" | head -1) if [[ -n "$INNER_PATH" ]]; then unzip -q -o "$ORIGINAL_JAR" "META-INF/versions/$INNER_PATH" -d "$EXTRACT_DIR" DECOMPILE_TARGET="$EXTRACT_DIR/META-INF/versions/$INNER_PATH" echo "Extracted inner jar: $INNER_PATH" fi fi # Use Vineflower directly (bundled with MinecraftDecompiler, or standalone) VINEFLOWER_JAR="$MC_OFFICIAL/downloads/decompiler/vineflower.jar" if [[ ! -f "$VINEFLOWER_JAR" ]]; then # Fall back to vineflower bundled inside MinecraftDecompiler's cache VINEFLOWER_JAR=$(find "$MC_OFFICIAL" -name "vineflower*.jar" -not -name "MinecraftDecompiler.jar" 2>/dev/null | head -1) fi if [[ -z "$VINEFLOWER_JAR" || ! -f "$VINEFLOWER_JAR" ]]; then echo "Error: vineflower.jar not found. Place it at $MC_OFFICIAL/downloads/decompiler/vineflower.jar" exit 1 fi echo "Decompiling with Vineflower: $VINEFLOWER_JAR" java -jar "$VINEFLOWER_JAR" "$DECOMPILE_TARGET" "$DECOMPILED_DIR" fi echo "" echo "=== Done ===" echo "Decompiled source: $DECOMPILED_DIR" # --- For SERVER side, also ensure downloads//server.jar exists --- if [[ "$SIDE" == "SERVER" ]]; then DOWNLOADS_DIR="$MC_OFFICIAL/downloads/$VERSION" if [[ ! -f "$DOWNLOADS_DIR/server.jar" ]]; then mkdir -p "$DOWNLOADS_DIR" echo "" echo "Downloading server.jar for $VERSION into $DOWNLOADS_DIR ..." SERVER_JAR_URL=$(echo "$VERSION_META" | python3 -c " import json, sys data = json.load(sys.stdin) print(data['downloads']['server']['url']) ") if [[ -n "$SERVER_JAR_URL" ]]; then curl -L -o "$DOWNLOADS_DIR/server.jar" "$SERVER_JAR_URL" echo "Downloaded server.jar" else echo "Warning: could not download server.jar for $VERSION." fi else echo "server.jar already exists: $DOWNLOADS_DIR/server.jar" fi fi