Skip to content

Commit

Permalink
Add an option to download installers
Browse files Browse the repository at this point in the history
Matyrobbrt committed Jul 2, 2024
1 parent 65055c9 commit dbda9cb
Showing 2 changed files with 84 additions and 27 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -14,6 +14,14 @@ Afterwards, you may run the jar as an executable (i.e. `java -jar server.jar`).
## Compatible Versions
This starter is compatible with all [MinecraftForge](https://minecraftforge.net) versions since 1.17 and up to 1.20.1, and with all [NeoForge](https://neoforged.net) versions.

## Running the installer
If the starter cannot find the run scripts, it will attempt to run an installer.
It will first try to run the first file ending with `-installer.jar` from the folder.

You may specify an installer to download instead using the `--installer` option (i.e. `java -jar server.jar --installer 21.0.46-beta`).
The installer specified can either be a link to an installer (i.e. `https://maven.neoforged.net/releases/net/neoforged/neoforge/21.0.46-beta/neoforge-21.0.46-beta-installer.jar`)
or a **NeoForge version** to download the installer for (i.e. `21.0.46-beta`).

## How it works
Below you will find the steps the start goes through to launch a modular NeoForge environment:
1. search the folder the starter was invoked in for the `run.sh` (*nix) / `run.bat` (Windows) file
103 changes: 76 additions & 27 deletions src/main/java/net/neoforged/serverstarterjar/Main.java
Original file line number Diff line number Diff line change
@@ -6,13 +6,17 @@
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -29,29 +33,50 @@ public class Main {
public static final char SINGLE_QUOTES = '\'';
public static final OperatingSystem OS = detectOs();
public static final MethodHandle LOAD_MODULE;
public static final MethodHandle SET_BOOT_LAYER;
public static final MethodHandle SET_bootLayer;
public static final MethodHandle SET_installedProviders;
public static final MethodHandle loadInstalledProviders;

static {
// Open the needed packages below to ourselves
open(ModuleLayer.boot().findModule("java.base").orElseThrow(), "java.lang", Main.class.getModule());
export(ModuleLayer.boot().findModule("java.base").orElseThrow(), "jdk.internal.loader", Main.class.getModule());
open(ModuleLayer.boot().findModule("java.base").orElseThrow(), "java.nio.file.spi", Main.class.getModule());

var lookup = MethodHandles.lookup();
try {
LOAD_MODULE = lookup.unreflect(lookup.findClass("jdk.internal.loader.BuiltinClassLoader").getDeclaredMethod("loadModule", ModuleReference.class));
SET_BOOT_LAYER = MethodHandles.privateLookupIn(System.class, lookup).unreflectSetter(System.class.getDeclaredField("bootLayer"));
SET_bootLayer = MethodHandles.privateLookupIn(System.class, lookup).unreflectSetter(System.class.getDeclaredField("bootLayer"));

SET_installedProviders = MethodHandles.privateLookupIn(FileSystemProvider.class, lookup).findStaticSetter(FileSystemProvider.class, "installedProviders", List.class);
loadInstalledProviders = MethodHandles.privateLookupIn(FileSystemProvider.class, lookup).findStatic(FileSystemProvider.class, "loadInstalledProviders", MethodType.methodType(List.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static void main(String[] starterArgs) throws Throwable {
var startArgs = new ArrayList<>(Arrays.asList(starterArgs));

URL installerUrl = null;
if (startArgs.contains("--installer")) {
var installer = startArgs.get(startArgs.indexOf("--installer") + 1);
startArgs.remove("--installer");
startArgs.remove(installer);

if (installer.startsWith("https://")) {
installerUrl = URI.create(installer).toURL();
} else {
installerUrl = URI.create("https://maven.neoforged.net/releases/net/neoforged/neoforge/" + installer + "/neoforge-" + installer + "-installer.jar").toURL();
}
}

// Attempt to locate the run.bat/run.sh file
final var runPath = Path.of(OS.runFile);
if (Files.notExists(runPath)) {
// If it doesn't exist, attempt to find a file whose name ends in "installer.jar" and run it as an installer
System.err.println("Failed to find run file at " + runPath + ", attempting to run installer");
if (!runInstaller()) {
if (!runInstaller(installerUrl)) {
System.exit(1);
}
}
@@ -88,7 +113,17 @@ public static void main(String[] starterArgs) throws Throwable {
(bootPath.layer().findModule(toExport[1]).orElseThrow())
));

SET_BOOT_LAYER.invokeExact(bootPath.layer());
SET_bootLayer.invokeExact(bootPath.layer());

// Clear installed providers so the JiJ provider can be found
{
@SuppressWarnings("unchecked")
final List<FileSystemProvider> newProviders = (List<FileSystemProvider>) loadInstalledProviders.invokeExact();
// Insert default provider at the start of the list
newProviders.add(0, FileSystems.getDefault().provider());
// Update the installed providers
SET_installedProviders.invokeExact(newProviders);
}

var sysProps = args.stream().filter(arg -> arg.startsWith("-D")).toList();
sysProps.forEach(args::remove);
@@ -109,7 +144,7 @@ public static void main(String[] starterArgs) throws Throwable {
}

// Pass any args specified to the start jar to MC
args.addAll(Arrays.asList(starterArgs));
args.addAll(startArgs);

// If the main class isn't exported, export it so that we can access it
if (!main.getDeclaringClass().getModule().isExported(main.getDeclaringClass().getPackageName())) {
@@ -141,33 +176,47 @@ private static void open(Module module, String pkg, Module to) {
);
}

private static boolean runInstaller() throws Throwable {
try (final var stream = Files.find(Path.of("."), 1, (path, basicFileAttributes) -> path.getFileName().toString().endsWith("installer.jar"))) {
var inst = stream.findFirst();
if (inst.isPresent()) {
var installer = inst.get();
System.err.println("Found installer " + installer.toAbsolutePath());

var installerJar = new JarFile(installer.toFile());
var manifest = installerJar.getManifest();
installerJar.close();
var mainName = manifest.getMainAttributes().getValue("Main-Class");
if (mainName == null) {
System.err.println("Installer file doesn't specify Main-Class");
return false;
private static boolean runInstaller(@Nullable URL installerUrl) throws Throwable {
Path installer = null;

if (installerUrl != null) {
var onSlash = installerUrl.getPath().split("/");
installer = Path.of(onSlash[onSlash.length - 1]);
System.err.println("Downloading installer from " + installerUrl + " to " + installer.toAbsolutePath());
try (var stream = installerUrl.openStream()) {
Files.copy(stream, installer);
}
} else {
try (final var stream = Files.find(Path.of("."), 1, (path, basicFileAttributes) -> path.getFileName().toString().endsWith("installer.jar"))) {
var inst = stream.findFirst();
if (inst.isPresent()) {
installer = inst.get();
}
}
}

var classLoader = new URLClassLoader(new URL[]{ installer.toUri().toURL() });
if (installer != null) {
System.err.println("Found installer " + installer.toAbsolutePath());

var mainClass = classLoader.loadClass(mainName);
System.err.println("Running installer...");
var installerJar = new JarFile(installer.toFile());
var manifest = installerJar.getManifest();
installerJar.close();
var mainName = manifest.getMainAttributes().getValue("Main-Class");
if (mainName == null) {
System.err.println("Installer file doesn't specify Main-Class");
return false;
}

mainClass.getDeclaredMethod("main", String[].class).invoke(null, (Object) new String[] { "--installServer" });
var classLoader = new URLClassLoader(new URL[]{ installer.toUri().toURL() });

System.err.println("Installer finished");
classLoader.close();
return true;
}
var mainClass = classLoader.loadClass(mainName);
System.err.println("Running installer...");

mainClass.getDeclaredMethod("main", String[].class).invoke(null, (Object) new String[] { "--installServer" });

System.err.println("Installer finished");
classLoader.close();
return true;
}

return false;

0 comments on commit dbda9cb

Please sign in to comment.