Resource gatherer to extract data from Minecraft's server jar instead of distributing our own

This commit is contained in:
jglrxavpok 2020-05-01 21:29:34 +02:00
parent 7493a6cae7
commit feae9f1cd3
7 changed files with 221 additions and 120018 deletions

3
.gitignore vendored
View File

@ -42,6 +42,9 @@ gradle-app.setting
# Cache of project
.gradletasknamecache
# IDEA files
.idea/
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,13 @@ import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.RegistryMain;
import net.minestom.server.registry.ResourceGatherer;
import net.minestom.server.scoreboard.TeamManager;
import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.world.Difficulty;
import java.io.IOException;
public class MinecraftServer {
// Threads
@ -103,6 +106,12 @@ public class MinecraftServer {
nettyServer = new NettyServer(packetProcessor);
// Registry
try {
ResourceGatherer.ensureResourcesArePresent(null); // TODO: provide a way to give a path override, probably via launch arguments?
} catch (IOException e) {
System.err.println("An error happened during resource gathering. Minestom will attempt to load anyway, but things may not work, and crashes can happen.");
e.printStackTrace();
}
RegistryMain.registerBlocks();
RegistryMain.registerItems();
RegistryMain.registerEntities();

View File

@ -23,12 +23,12 @@ import java.util.Set;
public class RegistryMain {
public static final String BLOCKS_PATH = "registry/blocks.json";
public static final String ITEMS_PATH = "registry/registries.json";
public static final String ENTITIES_PATH = "registry/registries.json";
public static final String SOUNDS_PATH = "registry/registries.json";
public static final String PARTICLES_PATH = "registry/registries.json";
public static final String STATS_PATH = "registry/registries.json";
public static final String BLOCKS_PATH = "minecraft_data/reports/blocks.json";
public static final String ITEMS_PATH = "minecraft_data/reports/registries.json";
public static final String ENTITIES_PATH = "minecraft_data/reports/registries.json";
public static final String SOUNDS_PATH = "minecraft_data/reports/registries.json";
public static final String PARTICLES_PATH = "minecraft_data/reports/registries.json";
public static final String STATS_PATH = "minecraft_data/reports/registries.json";
public static void main(String[] args) {
List<RegistryBlock> blocks = parseBlocks(BLOCKS_PATH);

View File

@ -0,0 +1,164 @@
package net.minestom.server.registry;
import com.google.gson.Gson;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Collectors;
/**
* Responsible for making sure Minestom has the necessary files to run (notably registry files)
*/
public class ResourceGatherer {
public static final File DATA_FOLDER = new File("./minecraft_data/");
private static final File TMP_FOLDER = new File("./.minestom_tmp/");
/**
* Checks if registry/ folder is present
* If it is not, download the minecraft server jar, run the data generator and extract the wanted files
* If it is already present, directly return
*/
public static void ensureResourcesArePresent(File minecraftFolderOverride) throws IOException {
if(DATA_FOLDER.exists()) {
return;
}
System.out.println(DATA_FOLDER +" folder does not exist. Minestom will now generate the necessary files.");
if(!TMP_FOLDER.exists() && !TMP_FOLDER.mkdirs()) {
throw new IOException("Failed to create tmp folder.");
}
final String version = "1.15.2"; // TODO: Do not hardcode
System.out.println("Starting download of Minecraft server jar for version "+version+" from Mojang servers...");
File minecraftFolder = getMinecraftFolder(minecraftFolderOverride);
if(!minecraftFolder.exists()) {
throw new IOException("Could not find Minecraft installation folder, attempted location "+minecraftFolder+". If this location is not the correct one, please supply the correct one as argument of ResourceGatherer#ensureResourcesArePresent");
}
File serverJar = downloadServerJar(minecraftFolder, version);
System.out.println("Download complete.");
runDataGenerator(serverJar);
moveAndCleanup(version);
System.out.println("Resource gathering done!");
}
private static void moveAndCleanup(String version) throws IOException {
Path dataFolderPath = DATA_FOLDER.toPath();
Path tmpFolderPath = TMP_FOLDER.toPath();
Path generatedFolder = tmpFolderPath.resolve("generated");
System.out.println("Data generator successful, removing server jar");
Files.delete(tmpFolderPath.resolve("server_"+version+".jar"));
System.out.println("Removal successful, now moving data to "+DATA_FOLDER);
Files.walkFileTree(tmpFolderPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path relativePath = generatedFolder.relativize(dir);
if(dir.startsWith(generatedFolder)) { // don't copy logs
Path resolvedPath = dataFolderPath.resolve(relativePath);
System.out.println("> Creating sub-folder "+relativePath);
Files.createDirectories(resolvedPath);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("> Deleting folder "+dir);
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path relativePath = generatedFolder.relativize(file);
if(file.startsWith(generatedFolder)) { // don't copy logs
Path resolvedPath = dataFolderPath.resolve(relativePath);
System.out.println("> Moving "+relativePath);
Files.move(file, resolvedPath);
} else {
System.out.println("> Deleting "+relativePath);
Files.delete(file);
}
return FileVisitResult.CONTINUE;
}
});
}
private static void runDataGenerator(File serverJar) throws IOException {
ProcessBuilder dataGenerator = new ProcessBuilder("java", "-cp", serverJar.getName(), "net.minecraft.data.Main", "--all", "--server", "--dev");
dataGenerator.directory(TMP_FOLDER);
dataGenerator.inheritIO();
System.out.println("Now running data generator with options '--dev', '--server', '--all'");
System.out.println("Executing: "+dataGenerator.command().stream().collect(Collectors.joining(" ")));
System.out.println("Minestom will now wait for it to finish, here's its output:");
Process dataGeneratorProcess = dataGenerator.start();
try {
int resultCode = dataGeneratorProcess.waitFor();
if(resultCode != 0) {
throw new IOException("Data generator finished with non-zero return code "+resultCode);
}
} catch (InterruptedException e) {
throw new IOException("Data generator was interrupted.", e);
}
}
/**
* Finds the URL for the server jar inside the versions/ folder of the game installation and download the .jar file from there
* @param minecraftFolder
* @param version
* @return
*/
private static File downloadServerJar(File minecraftFolder, String version) throws IOException {
File versionInfoFile = new File(minecraftFolder, "versions/"+version+"/"+version+".json");
if(!versionInfoFile.exists()) {
throw new IOException("Could not find "+version+".json in your Minecraft installation. Make sure to launch this version at least once before running Minestom");
}
try(FileReader fileReader = new FileReader(versionInfoFile)) {
Gson gson = new Gson();
VersionInfo versionInfo = gson.fromJson(fileReader, VersionInfo.class);
VersionInfo.DownloadObject serverJarInfo = versionInfo.getDownloadableFiles().get("server");
String downloadURL = serverJarInfo.getUrl();
System.out.println("Found URL, starting download from "+downloadURL+"...");
return download(version, downloadURL);
}
}
private static File download(String version, String url) throws IOException {
File target = new File(TMP_FOLDER, "server_"+version+".jar");
try(BufferedInputStream in = new BufferedInputStream(new URL(url).openStream())) {
Files.copy(in, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new IOException("Failed to download Minecraft server jar.", e);
}
return target;
}
private static File getMinecraftFolder(File minecraftFolderOverride) {
if(minecraftFolderOverride != null) {
return minecraftFolderOverride;
}
// https://help.minecraft.net/hc/en-us/articles/360035131551-Where-are-Minecraft-files-stored-
String os = System.getProperty("os.name").toLowerCase();
if(os.contains("win")) {
String user = System.getProperty("user.name");
return new File("C:/Users/"+user+"/AppData/Roaming/.minecraft/");
}
if(os.contains("mac")) {
return new File("~/Library/Application Support/minecraft");
}
return new File("~/.minecraft");
}
}

View File

@ -0,0 +1,39 @@
package net.minestom.server.registry;
import java.util.Map;
/**
* Class used to represent the contents of a versions/{version}/{version}.json file
* Structured in a way that makes loading with Gson easy.
* Only concerned with helping extracting data from the server jar, lots of features may be missing.
*/
class VersionInfo {
private Map<String, DownloadObject> downloads;
private VersionInfo() {
}
public Map<String, DownloadObject> getDownloadableFiles() {
return downloads;
}
class DownloadObject {
private String url;
private String sha1;
private long size;
public String getUrl() {
return url;
}
public long getSize() {
return size;
}
public String getSha1() {
return sha1;
}
}
}