Refactor Minecraft setup process and remove unused classes
- Updated SetupMinecraft task to use a Python script for setup. - Removed dependencies on the Ripper task and related classes. - Added new PythonTasks class to handle Python script execution. - Introduced new scripts for handling Minecraft server setup and HTTP requests. - Cleaned up unnecessary code and files related to the previous implementation.
This commit is contained in:
@@ -24,8 +24,6 @@ subprojects {
|
||||
tasks.register('setupMinecraft', SetupMinecraft) {
|
||||
group = 'torchcs'
|
||||
description = 'Sets up the Minecraft server environment'
|
||||
dependsOn project(':ripper').tasks.named('jar')
|
||||
ripperJar.set(project(':ripper').tasks.named('jar', Jar).flatMap { it.archiveFile })
|
||||
}
|
||||
|
||||
tasks.register('configureNativeRelease', Exec) {
|
||||
|
||||
@@ -11,5 +11,4 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package torchcs.tasks;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
|
||||
public class PythonTasks {
|
||||
|
||||
private final File projectDir;
|
||||
|
||||
public PythonTasks(File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
}
|
||||
|
||||
public static TaskProvider<Task> CreateTask(Project project, String name, String pythonFile, String... args) {
|
||||
return project.getTasks().register(name, task -> {
|
||||
task.doLast(t -> new PythonTasks(project.getProjectDir()).execute(t.getLogger(), pythonFile, args));
|
||||
});
|
||||
}
|
||||
|
||||
public void execute(Logger logger, String pythonFile, String... args) {
|
||||
List<String> cmd = new ArrayList<>();
|
||||
cmd.add(python());
|
||||
cmd.add("-B");
|
||||
cmd.add(new File(projectDir, pythonFile).getAbsolutePath());
|
||||
cmd.add("--root");
|
||||
cmd.add(projectDir.getAbsolutePath());
|
||||
cmd.addAll(Arrays.asList(args));
|
||||
|
||||
Process process;
|
||||
|
||||
try {
|
||||
process = new ProcessBuilder(cmd)
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
logger.lifecycle(line);
|
||||
}
|
||||
|
||||
int exit = process.waitFor();
|
||||
if (exit != 0) throw new RuntimeException(pythonFile + " exited with code " + exit);
|
||||
} catch (InterruptedException e) {
|
||||
process.destroyForcibly();
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Build cancelled", e);
|
||||
} catch (IOException e) {
|
||||
process.destroyForcibly();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String python() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("win") ? "python" : "python3";
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,19 @@
|
||||
package torchcs.tasks;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.Directory;
|
||||
import org.gradle.api.file.ProjectLayout;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import torchcs.tasks.common.HttpClientMinecraftApi;
|
||||
import torchcs.tasks.common.ProjectPath;
|
||||
|
||||
public abstract class SetupMinecraft extends DefaultTask {
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getRipperJar();
|
||||
|
||||
@Inject
|
||||
public abstract ProjectLayout getLayout();
|
||||
private ProjectPath projectPath;
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException, InterruptedException {
|
||||
init();
|
||||
setupMinecraftJava();
|
||||
}
|
||||
|
||||
private void setupMinecraftJava() throws IOException, InterruptedException {
|
||||
JsonObject metadata = HttpClientMinecraftApi.fetchLatestJavaServerMetadata();
|
||||
|
||||
if (metadata == null) {
|
||||
getLogger().error("Failed to fetch latest Java server metadata.");
|
||||
return;
|
||||
}
|
||||
|
||||
String downloadUrl = metadata.get("downloads").getAsJsonObject().get("server").getAsJsonObject().get("url").getAsString();
|
||||
String versionId = metadata.get("id").getAsString();
|
||||
Directory versionPath = projectPath.getJavaPath().dir(versionId);
|
||||
|
||||
if(new File(versionPath.getAsFile(), ".setup-complete").exists()) {
|
||||
getLogger().lifecycle("Minecraft Java {} is already set up, skipping.", versionId);
|
||||
return;
|
||||
}
|
||||
|
||||
File marker = new File(versionPath.getAsFile(), ".setup-complete");
|
||||
versionPath.getAsFile().mkdirs();
|
||||
|
||||
File serverJar = versionPath.file("server.jar").getAsFile();
|
||||
if (!serverJar.exists()) {
|
||||
getLogger().lifecycle("Downloading Minecraft Java server {}...", versionId);
|
||||
HttpClientMinecraftApi.downloadFile(downloadUrl, versionPath);
|
||||
getLogger().lifecycle("Download complete.");
|
||||
}
|
||||
|
||||
getLogger().lifecycle("Decompiling server jar...");
|
||||
|
||||
runRipper(serverJar, versionPath.dir("src").getAsFile());
|
||||
|
||||
marker.createNewFile();
|
||||
getLogger().lifecycle("Decompilation complete.");
|
||||
getLogger().lifecycle("Setup finished successfully.");
|
||||
}
|
||||
|
||||
private void runRipper(File input, File output) throws IOException, InterruptedException {
|
||||
String java = ProcessHandle.current().info().command()
|
||||
.orElse(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
|
||||
|
||||
Process process = new ProcessBuilder(
|
||||
java,
|
||||
"-Xmx8g", "-Xms1g",
|
||||
"-XX:+UseZGC",
|
||||
"-XX:ConcGCThreads=4",
|
||||
"-jar", getRipperJar().getAsFile().get().getAbsolutePath(),
|
||||
input.getAbsolutePath(),
|
||||
output.getAbsolutePath(),
|
||||
"-t", String.valueOf(Runtime.getRuntime().availableProcessors())
|
||||
).redirectErrorStream(true).start();
|
||||
|
||||
Thread reader = new Thread(() -> {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
getLogger().lifecycle(line);
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
});
|
||||
reader.start();
|
||||
|
||||
int exit = process.waitFor();
|
||||
reader.join();
|
||||
if (exit != 0) throw new RuntimeException("Ripper exited with code " + exit);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
projectPath = new ProjectPath(getLayout());
|
||||
projectPath.initDirectories();
|
||||
public void run() {
|
||||
new PythonTasks(getLayout().getProjectDirectory().getAsFile())
|
||||
.execute(getLogger(), "scripts/setup_minecraft.py");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package torchcs.tasks.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.gradle.api.file.Directory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class HttpClientMinecraftApi {
|
||||
|
||||
private static final String BEDROCK_URL = "https://net-secondary.web.minecraft-services.net/api/v1.0/download/links";
|
||||
private static final String JAVA_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
private static final HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
public static JsonObject fetchBedrock() throws IOException, InterruptedException {
|
||||
return fetch(BEDROCK_URL);
|
||||
}
|
||||
|
||||
public static JsonObject fetchJava() throws IOException, InterruptedException {
|
||||
return fetch(JAVA_URL);
|
||||
}
|
||||
|
||||
public static JsonObject fetchLatestJavaServerMetadata() throws IOException, InterruptedException {
|
||||
JsonObject javaServerMetadata = fetchJava();
|
||||
String latestVersion = javaServerMetadata.get("latest").getAsJsonObject().get("release").getAsString();
|
||||
|
||||
JsonArray versions = javaServerMetadata.getAsJsonArray("versions");
|
||||
|
||||
for (var element : versions) {
|
||||
JsonObject version = element.getAsJsonObject();
|
||||
String id = version.get("id").getAsString();
|
||||
if (id.equals(latestVersion)) {
|
||||
String url = version.get("url").getAsString();
|
||||
return fetch(url);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void downloadFile(String url, Directory outPath) throws IOException, InterruptedException {
|
||||
String fileName = url.substring(url.lastIndexOf('/') + 1);
|
||||
Path destination = outPath.getAsFile().toPath().resolve(fileName);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("User-Agent", "TorchCS/1.0")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
Files.copy(response.body(), destination, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
public static JsonObject fetch(String url) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("User-Agent", "TorchCS/1.0")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package torchcs.tasks.common;
|
||||
|
||||
import org.gradle.api.file.Directory;
|
||||
import org.gradle.api.file.ProjectLayout;
|
||||
|
||||
public class ProjectPath {
|
||||
|
||||
private final ProjectLayout layout;
|
||||
|
||||
public ProjectPath(ProjectLayout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
public Directory getBinPath() {
|
||||
return layout.getProjectDirectory().dir("bin");
|
||||
}
|
||||
|
||||
public Directory getMinecraftPath() {
|
||||
return getBinPath().dir("minecraft");
|
||||
}
|
||||
|
||||
public Directory getJavaPath() {
|
||||
return getMinecraftPath().dir("java");
|
||||
}
|
||||
|
||||
public Directory getBedrockPath() {
|
||||
return getMinecraftPath().dir("bedrock");
|
||||
}
|
||||
|
||||
public void initDirectories() {
|
||||
|
||||
Directory path;
|
||||
|
||||
path = getBinPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
path = getMinecraftPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
path = getJavaPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
|
||||
path = getBedrockPath();
|
||||
|
||||
if (!path.getAsFile().exists()) {
|
||||
path.getAsFile().mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
requests
|
||||
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def _java_executable() -> str:
|
||||
java_home = os.environ.get("JAVA_HOME")
|
||||
|
||||
if java_home:
|
||||
return str(Path(java_home) / "bin" / "java")
|
||||
|
||||
return "java"
|
||||
@@ -0,0 +1,36 @@
|
||||
import json
|
||||
import shutil
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
BEDROCK_URL = "https://net-secondary.web.minecraft-services.net/api/v1.0/download/links"
|
||||
JAVA_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
||||
HEADERS = {"User-Agent": "TorchCS/1.0"}
|
||||
|
||||
def fetch(url: str) -> dict:
|
||||
req = urllib.request.Request(url, headers=HEADERS)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
return json.loads(response.read().decode())
|
||||
def fetch_bedrock() -> dict:
|
||||
return fetch(BEDROCK_URL)
|
||||
|
||||
def fetch_java() -> dict:
|
||||
return fetch(JAVA_URL)
|
||||
|
||||
def fetch_latest_java_server_metadata() -> dict | None:
|
||||
manifest = fetch_java()
|
||||
latest_version = manifest["latest"]["release"]
|
||||
|
||||
for version in manifest["versions"]:
|
||||
if version["id"] == latest_version:
|
||||
return fetch(version["url"])
|
||||
|
||||
return None
|
||||
|
||||
def download_file(url: str, out_path: str | Path) -> None:
|
||||
file_name = url.rsplit("/", 1)[-1]
|
||||
destination = Path(out_path) / file_name
|
||||
|
||||
req = urllib.request.Request(url, headers=HEADERS)
|
||||
with urllib.request.urlopen(req) as response, open(destination, "wb") as out_file:
|
||||
shutil.copyfileobj(response, out_file)
|
||||
@@ -0,0 +1,87 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from common.constants import _java_executable
|
||||
from common.http_client_minecraft import download_file, fetch_latest_java_server_metadata
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
log = logging.getLogger("SetupMinecraft")
|
||||
|
||||
def extract_jar(server_jar: Path, version_id: str) -> Path:
|
||||
extract_dir = server_jar.parent / "extracted"
|
||||
with zipfile.ZipFile(server_jar) as zf:
|
||||
zf.extractall(extract_dir)
|
||||
|
||||
return extract_dir / f"META-INF/versions/{version_id}/server-{version_id}.jar"
|
||||
|
||||
def run_ripper(ripper_jar: Path, input_jar: Path, output_dir: Path) -> None:
|
||||
java = _java_executable()
|
||||
cmd = [java, "-Xmx8g", "-Xms1g", "-XX:+UseZGC", "-XX:ConcGCThreads=4", "-jar", str(ripper_jar), str(input_jar), str(output_dir), "-t", str(os.cpu_count())]
|
||||
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
|
||||
for line in process.stdout:
|
||||
log.info(line.rstrip())
|
||||
|
||||
process.wait()
|
||||
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError(f"Ripper exited with code {process.returncode}")
|
||||
|
||||
|
||||
def setup_minecraft_java(ripper_jar: Path, java_path: Path) -> None:
|
||||
metadata = fetch_latest_java_server_metadata()
|
||||
|
||||
if metadata is None:
|
||||
log.error("Failed to fetch latest Java server metadata.")
|
||||
return
|
||||
|
||||
download_url = metadata["downloads"]["server"]["url"]
|
||||
version_id = metadata["id"]
|
||||
version_path = java_path / version_id
|
||||
|
||||
if (version_path / ".setup-complete").exists():
|
||||
log.info("Minecraft Java %s is already set up, skipping.", version_id)
|
||||
return
|
||||
|
||||
version_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
server_jar = version_path / "server.jar"
|
||||
|
||||
if not server_jar.exists():
|
||||
log.info("Downloading Minecraft Java server %s...", version_id)
|
||||
download_file(download_url, version_path)
|
||||
|
||||
log.info("Download complete.")
|
||||
|
||||
log.info("Decompiling server jar...")
|
||||
|
||||
|
||||
inner_jar = extract_jar(server_jar, version_id)
|
||||
|
||||
run_ripper(ripper_jar, inner_jar, inner_jar.parent / "src")
|
||||
|
||||
(version_path / ".setup-complete").touch()
|
||||
|
||||
log.info("Decompilation complete.")
|
||||
log.info("Setup finished successfully.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Set up a Minecraft Java server and decompile it.")
|
||||
parser.add_argument("--root", required=True, type=Path, help="Project root directory")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ripper_jar = args.root / "build" / "gradle" / "ripper" / "libs" / "ripper.jar"
|
||||
java_path = args.root / "bin" / "minecraft" / "java"
|
||||
|
||||
setup_minecraft_java(ripper_jar, java_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,9 +4,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
@@ -56,17 +54,15 @@ public class Ripper {
|
||||
}
|
||||
|
||||
private static void decompile(File inputJar, File outputDir, int threads, boolean verbose) throws IOException {
|
||||
File targetJar = extractInnerServerJar(inputJar);
|
||||
|
||||
int threadCount = threads <= 0 ? Runtime.getRuntime().availableProcessors() : threads;
|
||||
int totalClasses = countRootClasses(targetJar);
|
||||
int totalClasses = countRootClasses(inputJar);
|
||||
int width = String.valueOf(totalClasses).length();
|
||||
String sep = "─".repeat(40 + width * 2);
|
||||
|
||||
System.out.println();
|
||||
System.out.println(" Ripper — Java Decompiler");
|
||||
System.out.println(" " + sep);
|
||||
System.out.printf(" Input : %s%n", targetJar.getName());
|
||||
System.out.printf(" Input : %s%n", inputJar.getName());
|
||||
System.out.printf(" Output : %s%n", outputDir.getAbsolutePath());
|
||||
System.out.printf(" Threads : %d%n", threadCount);
|
||||
System.out.printf(" Classes : ~%d%n", totalClasses);
|
||||
@@ -93,18 +89,14 @@ public class Ripper {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
DirectoryResultSaver saver = new DirectoryResultSaver(outputDir, totalClasses);
|
||||
BaseDecompiler decompiler = new BaseDecompiler(provider, saver, options, logger);
|
||||
decompiler.addSource(targetJar);
|
||||
BaseDecompiler decompiler = new BaseDecompiler(provider, saver, options, logger, new ShutdownCancellationManager());
|
||||
decompiler.addSource(inputJar);
|
||||
decompiler.decompileContext();
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println();
|
||||
System.out.printf(" Done: %d files written in %s%n", saver.getWritten(), formatDuration(elapsed));
|
||||
System.out.println();
|
||||
|
||||
if (!targetJar.equals(inputJar)) {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static int countRootClasses(File jar) throws IOException {
|
||||
@@ -133,21 +125,4 @@ public class Ripper {
|
||||
System.err.println("[Ripper/ERROR] " + message);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static File extractInnerServerJar(File bundlerJar) throws IOException {
|
||||
try (JarFile jar = new JarFile(bundlerJar)) {
|
||||
Optional<JarEntry> serverEntry = jar.stream()
|
||||
.filter(e -> e.getName().startsWith("META-INF/versions/") && e.getName().endsWith(".jar"))
|
||||
.findFirst();
|
||||
|
||||
if (serverEntry.isPresent()) {
|
||||
File extracted = new File(bundlerJar.getParentFile(), "server-inner.jar");
|
||||
try (InputStream is = jar.getInputStream(serverEntry.get())) {
|
||||
Files.copy(is, extracted.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return extracted;
|
||||
}
|
||||
}
|
||||
return bundlerJar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.torchcs.ripper;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.CancellationManager;
|
||||
|
||||
public class ShutdownCancellationManager implements CancellationManager {
|
||||
|
||||
private volatile boolean cancelled = false;
|
||||
|
||||
public ShutdownCancellationManager() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> cancelled = true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() throws CanceledException {
|
||||
if (cancelled) throw new CanceledException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMethod(String className, String methodName) {}
|
||||
|
||||
@Override
|
||||
public void finishMethod(String className, String methodName) {}
|
||||
}
|
||||
Reference in New Issue
Block a user