diff --git a/README.md b/README.md index 2a8a9b5..f490261 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ Allow [MultiMC](https://github.com/MultiMC/MultiMC5) to launch Minecraft 1.13+ with Forge. -## How to use +**ForgeWrapper has been adopted by MultiMC, you do not need to perform the following steps manually. (2020-03-29)** + +## How to use (Outdated) -### Install Forge Only 1. Download Forge installer for Minecraft 1.13+ [here](https://files.minecraftforge.net/). 2. Download ForgeWrapper jar file at the [release](https://github.com/ZekerZhayard/ForgeWrapper/releases) page. 3. Run the below command in terminal: @@ -14,17 +15,4 @@ Allow [MultiMC](https://github.com/MultiMC/MultiMC5) to launch Minecraft 1.13+ w *Notice: If you don't specify a MultiMC instance path, ForgeWrapper will create the instance folder in current working space.* 4. If the instance folder which just created is not in `MultiMC/instances` folder, you just need to move to the `MultiMC/instances` folder. -5. Run MultiMC, and you will see a new instance named `forge--`. - -### Install CurseForge Modpack -1. Download the modpack zip file. -2. Download ForgeWrapper jar file at the [release](https://github.com/ZekerZhayard/ForgeWrapper/releases) page. -3. Run the below command in terminal: - ``` - java -jar --cursepack= [--instance=] - ``` - *Notice: If you don't specify a MultiMC instance path, ForgeWrapper will create the instance folder in current working space.* - -4. If the instance folder which just created is not in `MultiMC/instances` folder, you just need to move to the `MultiMC/instances` folder. -5. Run MultiMC, and you will see a new instance named `-`. -*Notice: CurseForge modpack will be installed on first launch by [cursepacklocator](https://github.com/cpw/cursepacklocator), it will take a few minutes.* \ No newline at end of file +5. Run MultiMC, and you will see a new instance named `forge--`. \ No newline at end of file diff --git a/build.gradle b/build.gradle index d1dfebe..c501600 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ apply plugin: "idea" sourceCompatibility = targetCompatibility = 1.8 -version = "1.4.0" +version = "1.4.1" group = "io.github.zekerzhayard" archivesBaseName = rootProject.name diff --git a/src/main/java/cpw/mods/forge/cursepacklocator/Murmur2.java b/src/main/java/cpw/mods/forge/cursepacklocator/Murmur2.java deleted file mode 100644 index f754e18..0000000 --- a/src/main/java/cpw/mods/forge/cursepacklocator/Murmur2.java +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright 2014 Prasanth Jayachandran - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cpw.mods.forge.cursepacklocator; - -/** - * Murmur2 32 and 64 bit variants. - * 32-bit Java port of https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp#37 - * 64-bit Java port of https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp#96 - */ -public class Murmur2 { - // Constants for 32-bit variant - private static final int M_32 = 0x5bd1e995; - private static final int R_32 = 24; - - // Constants for 64-bit variant - private static final long M_64 = 0xc6a4a7935bd1e995L; - private static final int R_64 = 47; - private static final int DEFAULT_SEED = 0; - - /** - * Murmur2 32-bit variant. - * - * @param data - input byte array - * @return - hashcode - */ - public static int hash32(byte[] data) { - return hash32(data, data.length, DEFAULT_SEED); - } - - /** - * Murmur2 32-bit variant. - * - * @param data - input byte array - * @param length - length of array - * @param seed - seed. (default 0) - * @return - hashcode - */ - public static int hash32(byte[] data, int length, int seed) { - int h = seed ^ length; - int len_4 = length >> 2; - - // body - for (int i = 0; i < len_4; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) - | ((data[i_4 + 1] & 0xff) << 8) - | ((data[i_4 + 2] & 0xff) << 16) - | ((data[i_4 + 3] & 0xff) << 24); - - // mix functions - k *= M_32; - k ^= k >>> R_32; - k *= M_32; - h *= M_32; - h ^= k; - } - - // tail - int len_m = len_4 << 2; - int left = length - len_m; - if (left != 0) { - // see https://github.com/cpw/cursepacklocator/pull/3 - if (left >= 3) { - h ^= (int) data[length - (left - 2)] << 16; - } - if (left >= 2) { - h ^= (int) data[length - (left - 1)] << 8; - } - if (left >= 1) { - h ^= (int) data[length - left]; - } - - h *= M_32; - } - - // finalization - h ^= h >>> 13; - h *= M_32; - h ^= h >>> 15; - - return h; - } - - /** - * Murmur2 64-bit variant. - * - * @param data - input byte array - * @return - hashcode - */ - public static long hash64(final byte[] data) { - return hash64(data, data.length, DEFAULT_SEED); - } - - /** - * Murmur2 64-bit variant. - * - * @param data - input byte array - * @param length - length of array - * @param seed - seed. (default 0) - * @return - hashcode - */ - public static long hash64(final byte[] data, int length, int seed) { - long h = (seed & 0xffffffffl) ^ (length * M_64); - int length8 = length >> 3; - - // body - for (int i = 0; i < length8; i++) { - final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) - | (((long) data[i8 + 1] & 0xff) << 8) - | (((long) data[i8 + 2] & 0xff) << 16) - | (((long) data[i8 + 3] & 0xff) << 24) - | (((long) data[i8 + 4] & 0xff) << 32) - | (((long) data[i8 + 5] & 0xff) << 40) - | (((long) data[i8 + 6] & 0xff) << 48) - | (((long) data[i8 + 7] & 0xff) << 56); - - // mix functions - k *= M_64; - k ^= k >>> R_64; - k *= M_64; - h ^= k; - h *= M_64; - } - - // tail - int tailStart = length8 << 3; - switch (length - tailStart) { - case 7: - h ^= (long) (data[tailStart + 6] & 0xff) << 48; - case 6: - h ^= (long) (data[tailStart + 5] & 0xff) << 40; - case 5: - h ^= (long) (data[tailStart + 4] & 0xff) << 32; - case 4: - h ^= (long) (data[tailStart + 3] & 0xff) << 24; - case 3: - h ^= (long) (data[tailStart + 2] & 0xff) << 16; - case 2: - h ^= (long) (data[tailStart + 1] & 0xff) << 8; - case 1: - h ^= (long) (data[tailStart] & 0xff); - h *= M_64; - } - - // finalization - h ^= h >>> R_64; - h *= M_64; - h ^= h >>> R_64; - - return h; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/Utils.java b/src/main/java/io/github/zekerzhayard/forgewrapper/Utils.java deleted file mode 100644 index f309d69..0000000 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/Utils.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.zekerzhayard.forgewrapper; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Locale; - -public class Utils { - private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - public static void download(String url, String location) throws Exception { - File localFile = new File(location); - localFile.getParentFile().mkdirs(); - if (localFile.isFile()) { - try { - System.out.println("Checking Fingerprints of installer..."); - String md5 = new BufferedReader(new InputStreamReader(new URL(url + ".md5").openConnection().getInputStream())).readLine(); - String sha1 = new BufferedReader(new InputStreamReader(new URL(url + ".sha1").openConnection().getInputStream())).readLine(); - if (!checkMD5(location, md5) || !checkSHA1(location, sha1)) { - System.out.println("Fingerprints do not match!"); - localFile.delete(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - if (!localFile.isFile()) { - if (localFile.isDirectory()) { - throw new RuntimeException(location + " must be a file!"); - } - System.out.println("Downloading forge installer... (" + url + " ---> " + location + ")"); - Files.copy(new URL(url).openConnection().getInputStream(), Paths.get(location), StandardCopyOption.REPLACE_EXISTING); - download(url, location); - } - } - - public static boolean checkMD5(String path, String hash) throws IOException, NoSuchAlgorithmException { - String md5 = new String(encodeHex(MessageDigest.getInstance("MD5").digest(Files.readAllBytes(Paths.get(path))))); - System.out.println("MD5: " + hash + " ---> " + md5); - return md5.toLowerCase(Locale.ENGLISH).equals(hash.toLowerCase(Locale.ENGLISH)); - } - - public static boolean checkSHA1(String path, String hash) throws IOException, NoSuchAlgorithmException { - String sha1 = new String(encodeHex(MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(Paths.get(path))))); - System.out.println("SHA-1: " + hash + " ---> " + sha1); - return sha1.toLowerCase(Locale.ENGLISH).equals(hash.toLowerCase(Locale.ENGLISH)); - } - - private static char[] encodeHex(final byte[] data) { - final int l = data.length; - final char[] out = new char[l << 1]; - for (int i = 0, j = 0; i < l; i++) { - out[j++] = DIGITS[(0xF0 & data[i]) >>> 4]; - out[j++] = DIGITS[0x0F & data[i]]; - } - return out; - } -} diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java b/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java index 983310a..403488b 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java @@ -9,7 +9,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,33 +21,27 @@ import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import io.github.zekerzhayard.forgewrapper.Utils; public class Converter { - public static void convert(Path installerPath, Path targetDir, Path multimcDir, String cursepack) throws Exception { - if (cursepack != null) { - installerPath = getForgeInstallerFromCursePack(cursepack); - } - + public static void convert(Path installerPath, Path targetDir, Path multimcDir) throws Exception { JsonObject installer = getJsonFromZip(installerPath, "version.json"); JsonObject installProfile = getJsonFromZip(installerPath, "install_profile.json"); List arguments = getAdditionalArgs(installer); String mcVersion = arguments.get(arguments.indexOf("--fml.mcVersion") + 1); String forgeVersion = arguments.get(arguments.indexOf("--fml.forgeVersion") + 1); String forgeFullVersion = "forge-" + mcVersion + "-" + forgeVersion; - String instanceName = cursepack == null ? forgeFullVersion : installerPath.toFile().getName().replace("-installer.jar", ""); StringBuilder wrapperVersion = new StringBuilder(); JsonObject pack = convertPackJson(mcVersion); - JsonObject patches = convertPatchesJson(installer, installProfile, mcVersion, forgeVersion, wrapperVersion, cursepack); + JsonObject patches = convertPatchesJson(installer, installProfile, mcVersion, forgeVersion, wrapperVersion); Files.createDirectories(targetDir); // Copy mmc-pack.json and instance.cfg to folder. - Path instancePath = targetDir.resolve(instanceName); + Path instancePath = targetDir.resolve(forgeFullVersion); Files.createDirectories(instancePath); Files.copy(new ByteArrayInputStream(pack.toString().getBytes(StandardCharsets.UTF_8)), instancePath.resolve("mmc-pack.json"), StandardCopyOption.REPLACE_EXISTING); - Files.copy(new ByteArrayInputStream(("InstanceType=OneSix\nname=" + instanceName).getBytes(StandardCharsets.UTF_8)), instancePath.resolve("instance.cfg"), StandardCopyOption.REPLACE_EXISTING); + Files.copy(new ByteArrayInputStream(("InstanceType=OneSix\nname=" + forgeFullVersion).getBytes(StandardCharsets.UTF_8)), instancePath.resolve("instance.cfg"), StandardCopyOption.REPLACE_EXISTING); // Copy ForgeWrapper to /libraries folder. Path librariesPath = instancePath.resolve("libraries"); @@ -60,22 +53,9 @@ public class Converter { Files.createDirectories(patchesPath); Files.copy(new ByteArrayInputStream(patches.toString().getBytes(StandardCharsets.UTF_8)), patchesPath.resolve("net.minecraftforge.json"), StandardCopyOption.REPLACE_EXISTING); - // Extract all curse pack entries to /.minecraft folder. - Path minecraftPath = instancePath.resolve(".minecraft"); - if (cursepack != null) { - ZipFile zip = new ZipFile(cursepack); - Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - Path targetFolder = minecraftPath.resolve(entry.getName()); - Files.createDirectories(targetFolder.getParent()); - Files.copy(zip.getInputStream(entry), targetFolder, StandardCopyOption.REPLACE_EXISTING); - } - } - // Copy forge installer to MultiMC/libraries/net/minecraftforge/forge/- folder. if (multimcDir != null) { - Path targetInstallerPath = multimcDir.resolve("net").resolve("minecraftforge").resolve("forge").resolve(forgeVersion); + Path targetInstallerPath = multimcDir.resolve("libraries").resolve("net").resolve("minecraftforge").resolve("forge").resolve(forgeVersion); Files.createDirectories(targetInstallerPath); Files.copy(installerPath, targetInstallerPath.resolve(forgeFullVersion + "-installer.jar"), StandardCopyOption.REPLACE_EXISTING); } @@ -101,28 +81,6 @@ public class Converter { } } - private static Path getForgeInstallerFromCursePack(String cursepack) throws Exception { - JsonObject manifest = getJsonFromZip(Paths.get(cursepack), "manifest.json"); - JsonObject minecraft = getElement(manifest, "minecraft").getAsJsonObject(); - String mcVersion = getElement(minecraft, "version").getAsString(); - String forgeVersion = null; - for (JsonElement element : getElement(minecraft, "modLoaders").getAsJsonArray()) { - String id = getElement(element.getAsJsonObject(), "id").getAsString(); - if (id.startsWith("forge-")) { - forgeVersion = id.replace("forge-", ""); - break; - } - } - if (forgeVersion == null) { - throw new RuntimeException("The curse pack is invalid!"); - } - String packName = getElement(manifest, "name").getAsString(); - String packVersion = getElement(manifest, "version").getAsString(); - Path installer = Paths.get(System.getProperty("java.io.tmpdir", "."), String.format("%s-%s-installer.jar", packName, packVersion)); - Utils.download(String.format("https://files.minecraftforge.net/maven/net/minecraftforge/forge/%s-%s/forge-%s-%s-installer.jar", mcVersion, forgeVersion, mcVersion, forgeVersion), installer.toString()); - return installer; - } - // Convert mmc-pack.json: // - Replace Minecraft version private static JsonObject convertPackJson(String mcVersion) { @@ -142,7 +100,7 @@ public class Converter { // - Add libraries // - Add forge-launcher url // - Replace Minecraft & Forge versions - private static JsonObject convertPatchesJson(JsonObject installer, JsonObject installProfile, String mcVersion, String forgeVersion, StringBuilder wrapperVersion, String cursepack) { + private static JsonObject convertPatchesJson(JsonObject installer, JsonObject installProfile, String mcVersion, String forgeVersion, StringBuilder wrapperVersion) { JsonObject patches = new JsonParser().parse(new InputStreamReader(Converter.class.getResourceAsStream("/patches/net.minecraftforge.json"))).getAsJsonObject(); JsonArray mavenFiles = getElement(patches, "mavenFiles").getAsJsonArray(); JsonArray libraries = getElement(patches, "libraries").getAsJsonArray(); @@ -160,12 +118,6 @@ public class Converter { wrapperVersion.append(getElement(lib.getAsJsonObject(), "MMC-filename").getAsString()); } } - if (cursepack != null) { - JsonObject cursepacklocator = new JsonObject(); - cursepacklocator.addProperty("name", "cpw.mods.forge:cursepacklocator:1.2.0"); - cursepacklocator.addProperty("url", "https://files.minecraftforge.net/maven/"); - libraries.add(cursepacklocator); - } Map additionalUrls = new HashMap<>(); String path = String.format("net/minecraftforge/forge/%s-%s/forge-%s-%s", mcVersion, forgeVersion, mcVersion, forgeVersion); additionalUrls.put(path + "-universal.jar", "https://files.minecraftforge.net/maven/" + path + "-universal.jar"); diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java b/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java index 312f9c3..83ecdf9 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java @@ -6,29 +6,18 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; -import io.github.zekerzhayard.forgewrapper.Utils; - public class Main { public static void main(String[] args) { Path installer = null, instance = Paths.get("."), multimc = null; - String cursepack = null; try { HashMap argsMap = parseArgs(args); - if (argsMap.containsKey("--installer")) { - installer = Paths.get(argsMap.get("--installer")); - } else { - installer = Paths.get(System.getProperty("java.io.tmpdir", "."), "gson-2.8.6.jar"); - Utils.download("https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", installer.toString()); - } + installer = Paths.get(argsMap.get("--installer")); if (argsMap.containsKey("--instance")) { instance = Paths.get(argsMap.get("--instance")); multimc = instance.getParent(); } - if (argsMap.containsKey("--cursepack")) { - cursepack = argsMap.get("--cursepack"); - } } catch (Exception e) { - System.out.println("Invalid arguments! Use: java -jar [--installer= | --cursepack=] [--instance=]"); + System.out.println("Invalid arguments! Use: java -jar --installer= [--instance=]"); throw new RuntimeException(e); } @@ -37,7 +26,7 @@ public class Main { Converter.class.getProtectionDomain().getCodeSource().getLocation(), installer.toUri().toURL() }, null); - ucl.loadClass("io.github.zekerzhayard.forgewrapper.converter.Converter").getMethod("convert", Path.class, Path.class, Path.class, String.class).invoke(null, installer, instance, multimc, cursepack); + ucl.loadClass("io.github.zekerzhayard.forgewrapper.converter.Converter").getMethod("convert", Path.class, Path.class, Path.class).invoke(null, installer, instance, multimc); System.out.println("Successfully install Forge for MultiMC!"); } catch (Exception e) { System.out.println("Failed to install Forge!"); @@ -56,7 +45,7 @@ public class Main { String[] params = arg.split("=", 2); map.put(params[0], params[1]); } - if (!map.containsKey("--installer") && !map.containsKey("--cursepack")) { + if (!map.containsKey("--installer")) { throw new IllegalArgumentException(); } return map; 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 f9d534c..bd97c19 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java @@ -23,12 +23,12 @@ public class Main { 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))) { - System.out.println("Some extra libraries are missing! Run installer to spawn them now."); + 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() - }, null); + }, getParentClassLoader()); Class installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer"); if (!(boolean) installer.getMethod("install").invoke(null)) { @@ -56,4 +56,16 @@ public class Main { minecraftDir.resolve(mcpFullVersion).resolve("client-" + mcpFullVersion + "-srg.jar") ); } + + // https://github.com/MinecraftForge/Installer/blob/fe18a164b5ebb15b5f8f33f6a149cc224f446dc2/src/main/java/net/minecraftforge/installer/actions/PostProcessors.java#L287-L303 + private static ClassLoader getParentClassLoader() { + if (!System.getProperty("java.version").startsWith("1.")) { + try { + return (ClassLoader) ClassLoader.class.getDeclaredMethod("getPlatformClassLoader").invoke(null); + } catch (Exception e) { + System.out.println("No platform classloader: " + System.getProperty("java.version")); + } + } + return null; + } }