summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZekerZhayard <trees1000@qq.com>2020-07-14 03:56:34 +0800
committerZekerZhayard <trees1000@qq.com>2020-07-14 03:56:34 +0800
commit29f602904a3ee7c3e92c3ee2036cf42bc10addb4 (patch)
tree4fbdb230ed3ae06131b45eb5de63570402f1d733
parent9f65af36b0b25292cf9455b66f7ee273fa1ff729 (diff)
downloadProject-Tick-29f602904a3ee7c3e92c3ee2036cf42bc10addb4.tar.gz
Project-Tick-29f602904a3ee7c3e92c3ee2036cf42bc10addb4.zip
Improve cache file detectors.
-rw-r--r--README.md8
-rw-r--r--build.gradle2
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/ClientInstall4MultiMC.java11
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java6
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java55
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java35
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java201
-rw-r--r--src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java46
-rw-r--r--src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector1
9 files changed, 327 insertions, 38 deletions
diff --git a/README.md b/README.md
index f490261c16..21e2b9640d 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,14 @@ Allow [MultiMC](https://github.com/MultiMC/MultiMC5) to launch Minecraft 1.13+ w
**ForgeWrapper has been adopted by MultiMC, you do not need to perform the following steps manually. (2020-03-29)**
+## For other launchers
+1. ForgeWrapper provides some java properties since 1.4.2:
+ - `forgewrapper.librariesDir` : a path to libraries folder (e.g. -Dforgewrapper.librariesDir=/home/xxx/.minecraft/libraries)
+ - `forgewrapper.installer` : a path to forge installer (e.g. -Dforgewrapper.installer=/home/xxx/forge-1.14.4-28.2.0-installer.jar)
+ - `forgewrapper.minecraft` : a path to the vanilla minecraft jar (e.g. -Dforgewrapper.minecraft=/home/xxx/.minecraft/versions/1.14.4/1.14.4.jar)
+
+2. ForgeWrapper also provides an interface [`IFileDetector`](https://github.com/ZekerZhayard/ForgeWrapper/blob/master/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java), you can implement it and custom your own detecting rules. To load it, you should make another jar which contains `META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector` within the full implementation class name and add the jar to class path.
+
## How to use (Outdated)
1. Download Forge installer for Minecraft 1.13+ [here](https://files.minecraftforge.net/).
diff --git a/build.gradle b/build.gradle
index c5016005c4..591fddd25c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ apply plugin: "idea"
sourceCompatibility = targetCompatibility = 1.8
-version = "1.4.1"
+version = "1.4.2"
group = "io.github.zekerzhayard"
archivesBaseName = rootProject.name
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/ClientInstall4MultiMC.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/ClientInstall4MultiMC.java
index f2db3748cf..ea7256416c 100644
--- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/ClientInstall4MultiMC.java
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/ClientInstall4MultiMC.java
@@ -8,14 +8,17 @@ import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
public class ClientInstall4MultiMC extends ClientInstall {
- public ClientInstall4MultiMC(Install profile, ProgressCallback monitor) {
+ protected File libraryDir;
+ protected File minecraftJar;
+
+ public ClientInstall4MultiMC(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar) {
super(profile, monitor);
+ this.libraryDir = libraryDir;
+ this.minecraftJar = minecraftJar;
}
@Override
public boolean run(File target, Predicate<String> optionals) {
- File librariesDir = Main.getLibrariesDir();
- File clientTarget = new File(String.format("%s/com/mojang/minecraft/%s/minecraft-%s-client.jar", librariesDir.getAbsolutePath(), this.profile.getMinecraft(), this.profile.getMinecraft()));
- return this.processors.process(librariesDir, clientTarget);
+ return this.processors.process(this.libraryDir, this.minecraftJar);
}
}
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java
index ad60ad7375..1a822ce06e 100644
--- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java
@@ -1,11 +1,13 @@
package io.github.zekerzhayard.forgewrapper.installer;
+import java.io.File;
+
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
import net.minecraftforge.installer.json.Util;
public class Installer {
- public static boolean install() {
+ public static boolean install(File libraryDir, File minecraftJar) {
ProgressCallback monitor = ProgressCallback.withOutputs(System.out);
Install install = Util.loadInstallProfile();
if (System.getProperty("java.net.preferIPv4Stack") == null) {
@@ -16,6 +18,6 @@ public class Installer {
String jvmVersion = System.getProperty("java.vm.version", "missing jvm version");
monitor.message(String.format("JVM info: %s - %s - %s", vendor, javaVersion, jvmVersion));
monitor.message("java.net.preferIPv4Stack=" + System.getProperty("java.net.preferIPv4Stack"));
- return new ClientInstall4MultiMC(install, monitor).run(null, input -> true);
+ return new ClientInstall4MultiMC(install, monitor, libraryDir, minecraftJar).run(null, input -> true);
}
}
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java
index bd97c192d1..c52c272493 100644
--- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java
@@ -1,7 +1,6 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.File;
-import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
@@ -11,50 +10,44 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import cpw.mods.modlauncher.Launcher;
+import io.github.zekerzhayard.forgewrapper.installer.detector.DetectorLoader;
+import io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector;
public class Main {
public static void main(String[] args) throws Exception {
List<String> argsList = Stream.of(args).collect(Collectors.toList());
String mcVersion = argsList.get(argsList.indexOf("--fml.mcVersion") + 1);
- String mcpFullVersion = mcVersion + "-" + argsList.get(argsList.indexOf("--fml.mcpVersion") + 1);
String forgeFullVersion = mcVersion + "-" + argsList.get(argsList.indexOf("--fml.forgeVersion") + 1);
- Path librariesDir = getLibrariesDir().toPath();
- Path minecraftDir = librariesDir.resolve("net").resolve("minecraft").resolve("client");
- Path forgeDir = librariesDir.resolve("net").resolve("minecraftforge").resolve("forge").resolve(forgeFullVersion);
- if (getAdditionalLibraries(minecraftDir, forgeDir, mcVersion, forgeFullVersion, mcpFullVersion).anyMatch(path -> !Files.exists(path))) {
+ IFileDetector detector = DetectorLoader.loadDetector();
+ if (!detector.checkExtraFiles(forgeFullVersion)) {
System.out.println("Some extra libraries are missing! Run the installer to generate them now.");
- URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {
- Main.class.getProtectionDomain().getCodeSource().getLocation(),
- Launcher.class.getProtectionDomain().getCodeSource().getLocation(),
- forgeDir.resolve("forge-" + forgeFullVersion + "-installer.jar").toUri().toURL()
- }, getParentClassLoader());
- Class<?> installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer");
- if (!(boolean) installer.getMethod("install").invoke(null)) {
- return;
+ // Check installer jar.
+ Path installerJar = detector.getInstallerJar(forgeFullVersion);
+ if (!IFileDetector.isFile(installerJar)) {
+ throw new RuntimeException("Can't detect the forge installer!");
}
- }
- Launcher.main(args);
- }
+ // Check vanilla Minecraft jar.
+ Path minecraftJar = detector.getMinecraftJar(mcVersion);
+ if (!IFileDetector.isFile(minecraftJar)) {
+ throw new RuntimeException("Can't detect the Minecraft jar!");
+ }
- public static File getLibrariesDir() {
- try {
- File laucnher = new File(Launcher.class.getProtectionDomain().getCodeSource().getLocation().toURI());
- // /<version> /modlauncher /mods /cpw /libraries
- return laucnher.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
+ try (URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {
+ Main.class.getProtectionDomain().getCodeSource().getLocation(),
+ Launcher.class.getProtectionDomain().getCodeSource().getLocation(),
+ installerJar.toUri().toURL()
+ }, getParentClassLoader())) {
+ Class<?> installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer");
+ if (!(boolean) installer.getMethod("install", File.class, File.class).invoke(null, detector.getLibraryDir().toFile(), minecraftJar.toFile())) {
+ return;
+ }
+ }
}
- }
- public static Stream<Path> getAdditionalLibraries(Path minecraftDir, Path forgeDir, String mcVersion, String forgeFullVersion, String mcpFullVersion) {
- return Stream.of(
- forgeDir.resolve("forge-" + forgeFullVersion + "-client.jar"),
- minecraftDir.resolve(mcVersion).resolve("client-" + mcVersion + "-extra.jar"),
- minecraftDir.resolve(mcpFullVersion).resolve("client-" + mcpFullVersion + "-srg.jar")
- );
+ Launcher.main(args);
}
// https://github.com/MinecraftForge/Installer/blob/fe18a164b5ebb15b5f8f33f6a149cc224f446dc2/src/main/java/net/minecraftforge/installer/actions/PostProcessors.java#L287-L303
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java
new file mode 100644
index 0000000000..369c4b555d
--- /dev/null
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java
@@ -0,0 +1,35 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class DetectorLoader {
+ public static IFileDetector loadDetector() {
+ ServiceLoader<IFileDetector> sl = ServiceLoader.load(IFileDetector.class);
+ HashMap<String, IFileDetector> detectors = new HashMap<>();
+ for (IFileDetector detector : sl) {
+ detectors.put(detector.name(), detector);
+ }
+
+ boolean enabled = false;
+ IFileDetector temp = null;
+ for (Map.Entry<String, IFileDetector> detector : detectors.entrySet()) {
+ HashMap<String, IFileDetector> others = new HashMap<>(detectors);
+ others.remove(detector.getKey());
+ if (!enabled) {
+ enabled = detector.getValue().enabled(others);
+ if (enabled) {
+ temp = detector.getValue();
+ }
+ } else if (detector.getValue().enabled(others)) {
+ throw new RuntimeException("There are two or more file detectors are enabled! (" + temp.toString() + ", " + detector.toString() + ")");
+ }
+ }
+
+ if (temp == null) {
+ throw new RuntimeException("No file detector is enabled!");
+ }
+ return temp;
+ }
+}
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java
new file mode 100644
index 0000000000..b81670840b
--- /dev/null
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java
@@ -0,0 +1,201 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import cpw.mods.modlauncher.Launcher;
+
+public interface IFileDetector {
+ /**
+ * @return The name of the detector.
+ */
+ String name();
+
+ /**
+ * If there are two or more detectors are enabled, an exception will be thrown. Removing anything from the map is in vain.
+ * @param others Other detectors.
+ * @return True represents enabled.
+ */
+ boolean enabled(HashMap<String, IFileDetector> others);
+
+ /**
+ * @return The ".minecraft/libraries" folder for normal. It can also be defined by JVM argument "-Dforgewrapper.librariesDir=&lt;libraries-path&gt;".
+ */
+ default Path getLibraryDir() {
+ String libraryDir = System.getProperty("forgewrapper.librariesDir");
+ if (libraryDir != null) {
+ return Paths.get(libraryDir).toAbsolutePath();
+ }
+ try {
+ Path launcher = Paths.get(Launcher.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toAbsolutePath();
+ // /<version> /modlauncher/mods /cpw /libraries
+ return launcher.getParent().getParent().getParent().getParent().getParent().toAbsolutePath();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
+ * @return The forge installer jar path. It can also be defined by JVM argument "-Dforgewrapper.installer=&lt;installer-path&gt;".
+ */
+ default Path getInstallerJar(String forgeFullVersion) {
+ String installer = System.getProperty("forgewrapper.installer");
+ if (installer != null) {
+ return Paths.get(installer).toAbsolutePath();
+ }
+ return null;
+ }
+
+ /**
+ * @param mcVersion Minecraft version (e.g. 1.14.4).
+ * @return The minecraft client jar path. It can also be defined by JVM argument "-Dforgewrapper.minecraft=&lt;minecraft-path&gt;".
+ */
+ default Path getMinecraftJar(String mcVersion) {
+ String minecraft = System.getProperty("forgewrapper.minecraft");
+ if (minecraft != null) {
+ return Paths.get(minecraft).toAbsolutePath();
+ }
+ return null;
+ }
+
+ /**
+ * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
+ * @return The json object in the-installer-jar-->install_profile.json-->data-->xxx-->client.
+ */
+ default JsonObject getInstallProfileExtraData(String forgeFullVersion) {
+ Path installer = this.getInstallerJar(forgeFullVersion);
+ if (isFile(installer)) {
+ try (ZipFile zf = new ZipFile(installer.toFile())) {
+ ZipEntry ze = zf.getEntry("install_profile.json");
+ if (ze != null) {
+ try (
+ InputStream is = zf.getInputStream(ze);
+ InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)
+ ) {
+ for (Map.Entry<String, JsonElement> entry : new JsonParser().parse(isr).getAsJsonObject().entrySet()) {
+ if (entry.getKey().equals("data")) {
+ return entry.getValue().getAsJsonObject();
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ throw new RuntimeException("Can't detect the forge installer!");
+ }
+ return null;
+ }
+
+ /**
+ * Check all cached files.
+ * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
+ * @return True represents all files are ready.
+ */
+ default boolean checkExtraFiles(String forgeFullVersion) {
+ JsonObject jo = this.getInstallProfileExtraData(forgeFullVersion);
+ if (jo != null) {
+ Map<String, Path> libsMap = new HashMap<>();
+ Map<String, String> hashMap = new HashMap<>();
+
+ // Get all "data/<name>/client" elements.
+ for (Map.Entry<String, JsonElement> entry : jo.entrySet()) {
+ String clientStr = getElement(entry.getValue().getAsJsonObject(), "client").getAsString();
+ if (entry.getKey().endsWith("_SHA")) {
+ Pattern p = Pattern.compile("^'(?<sha1>[A-Za-z0-9]{40})'$");
+ Matcher m = p.matcher(clientStr);
+ if (m.find()) {
+ hashMap.put(entry.getKey(), m.group("sha1"));
+ }
+ } else {
+ Pattern p = Pattern.compile("^\\[(?<groupId>[^:]*):(?<artifactId>[^:]*):(?<version>[^:@]*)(:(?<prefix>[^@]*))?(@(?<type>[^]]*))?]$");
+ Matcher m = p.matcher(clientStr);
+ if (m.find()) {
+ String groupId = nullToDefault(m.group("groupId"), "");
+ String artifactId = nullToDefault(m.group("artifactId"), "");
+ String version = nullToDefault(m.group("version"), "");
+ String prefix = nullToDefault(m.group("prefix"), "");
+ String type = nullToDefault(m.group("type"), "jar");
+ libsMap.put(entry.getKey(), this.getLibraryDir()
+ .resolve(groupId.replace('.', File.separatorChar))
+ .resolve(artifactId)
+ .resolve(version)
+ .resolve(artifactId + "-" + version + (prefix.equals("") ? "" : "-") + prefix + "." + type).toAbsolutePath());
+ }
+ }
+ }
+
+ // Check all cached libraries.
+ boolean checked = true;
+ for (Map.Entry<String, Path> entry : libsMap.entrySet()) {
+ checked = checked && this.checkExtraFile(entry.getValue(), hashMap.get(entry.getKey() + "_SHA"));
+ }
+ return checked;
+ }
+ // Skip installing process if installer profile doesn't exist.
+ return true;
+ }
+
+ /**
+ * Check the exact file.
+ * @param path The path of the file to check.
+ * @param sha1 The sha1 defined in installer.
+ * @return True represents the file is ready.
+ */
+ default boolean checkExtraFile(Path path, String sha1) {
+ return Files.isRegularFile(path) && (sha1 == null || sha1.equals("") || sha1.toLowerCase(Locale.ENGLISH).equals(getFileSHA1(path)));
+ }
+
+ static boolean isFile(Path path) {
+ return path != null && Files.isRegularFile(path);
+ }
+
+ static JsonElement getElement(JsonObject object, String property) {
+ Optional<Map.Entry<String, JsonElement>> first = object.entrySet().stream().filter(e -> e.getKey().equals(property)).findFirst();
+ if (first.isPresent()) {
+ return first.get().getValue();
+ }
+ return JsonNull.INSTANCE;
+ }
+
+ static String getFileSHA1(Path path) {
+ try {
+ StringBuilder sha1 = new StringBuilder(new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path))).toString(16));
+ while (sha1.length() < 40) {
+ sha1.insert(0, "0");
+ }
+ return sha1.toString().toLowerCase(Locale.ENGLISH);
+ } catch (IOException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ static String nullToDefault(String string, String defaultValue) {
+ return string == null ? defaultValue : string;
+ }
+}
diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java
new file mode 100644
index 0000000000..cb87d27825
--- /dev/null
+++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java
@@ -0,0 +1,46 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+
+public class MultiMCFileDetector implements IFileDetector {
+ protected Path libraryDir = null;
+ protected Path installerJar = null;
+ protected Path minecraftJar = null;
+
+ @Override
+ public String name() {
+ return "MultiMC";
+ }
+
+ @Override
+ public boolean enabled(HashMap<String, IFileDetector> others) {
+ return others.size() == 0;
+ }
+
+ @Override
+ public Path getLibraryDir() {
+ if (this.libraryDir == null) {
+ this.libraryDir = IFileDetector.super.getLibraryDir();
+ }
+ return this.libraryDir;
+ }
+
+ @Override
+ public Path getInstallerJar(String forgeFullVersion) {
+ Path path = IFileDetector.super.getInstallerJar(forgeFullVersion);
+ if (path == null) {
+ return this.installerJar != null ? this.installerJar : (this.installerJar = this.getLibraryDir().resolve("net").resolve("minecraftforge").resolve("forge").resolve(forgeFullVersion).resolve("forge-" + forgeFullVersion + "-installer.jar").toAbsolutePath());
+ }
+ return path;
+ }
+
+ @Override
+ public Path getMinecraftJar(String mcVersion) {
+ Path path = IFileDetector.super.getMinecraftJar(mcVersion);
+ if (path == null) {
+ return this.minecraftJar != null ? this.minecraftJar : (this.minecraftJar = this.getLibraryDir().resolve("com").resolve("mojang").resolve("minecraft").resolve(mcVersion).resolve("minecraft-" + mcVersion + "-client.jar").toAbsolutePath());
+ }
+ return path;
+ }
+}
diff --git a/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector b/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector
new file mode 100644
index 0000000000..44375f8732
--- /dev/null
+++ b/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector
@@ -0,0 +1 @@
+io.github.zekerzhayard.forgewrapper.installer.detector.MultiMCFileDetector \ No newline at end of file