Add converter to convert vanilla version json to MultiMC instance json.

This commit is contained in:
ZekerZhayard 2020-02-09 11:26:14 +08:00
parent c45356218a
commit 1b50883f6d
13 changed files with 261 additions and 204 deletions

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# ForgeWrapper
Allow MultiMC to launch Minecraft 1.13+ with Forge.
## How to use
1. Download Forge installer for Minecraft 1.13+ at [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:
```
java -jar <ForgeWrapper.jar> [--installer] <forge-installer.jar> [--instance <instance-path>]
```
*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-<mcVersion>-<forgeVersion>`.

View file

@ -4,7 +4,7 @@ apply plugin: "idea"
sourceCompatibility = targetCompatibility = 1.8
version = "1.0.0"
version = "1.1.0"
group = "io.github.zekerzhayard"
archivesBaseName = rootProject.name
@ -19,12 +19,23 @@ repositories {
dependencies {
compile "commons-codec:commons-codec:1.10"
compile "cpw.mods:modlauncher:5.0.0-milestone.4"
compile "net.minecraftforge:forge:1.15.1-30.0.16:installer"
compile "cpw.mods:modlauncher:4.1.0"
compile "net.minecraftforge:forge:1.14.4-28.2.0:installer"
}
jar {
manifest.attributes(
"Main-Class": "io.github.zekerzhayard.forgewrapper.Main"
"Main-Class": "io.github.zekerzhayard.forgewrapper.converter.Main"
)
}
processResources {
inputs.property "version", project.version
from(sourceSets.main.resources.srcDirs) {
include "patches/net.minecraftforge.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "patches/net.minecraftforge.json"
}
}

View file

@ -1,38 +0,0 @@
{
"components": [
{
"cachedName": "LWJGL 3",
"cachedVersion": "3.2.2",
"cachedVolatile": true,
"dependencyOnly": true,
"uid": "org.lwjgl3",
"version": "3.2.2"
},
{
"cachedName": "Minecraft",
"cachedRequires": [
{
"equals": "3.2.2",
"suggests": "3.2.2",
"uid": "org.lwjgl3"
}
],
"cachedVersion": "1.14.4",
"important": true,
"uid": "net.minecraft",
"version": "1.14.4"
},
{
"cachedName": "MinecraftForge",
"cachedRequires": [
{
"equals": "1.14.4",
"uid": "net.minecraft"
}
],
"cachedVersion": "1.14.4-28.1.109",
"uid": "net.minecraftforge"
}
],
"formatVersion": 1
}

View file

@ -1,107 +0,0 @@
{
"formatVersion": 1,
"libraries": [
{
"name": "com.github.ZekerZhayard:ForgeWrapper:-SNAPSHOT",
"url": "https://jitpack.io/"
},
{
"name": "net.minecraftforge:forge:1.14.4-28.1.109:launcher",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.ow2.asm:asm:6.2",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.ow2.asm:asm-commons:6.2",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.ow2.asm:asm-tree:6.2",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "cpw.mods:modlauncher:4.1.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "cpw.mods:grossjava9hacks:1.1.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecraftforge:accesstransformers:1.0.1-milestone.0.1+94458e7:shadowed",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecraftforge:eventbus:1.0.0:service",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecraftforge:forgespi:1.5.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecraftforge:coremods:1.0.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecraftforge:unsafe:0.2.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "com.electronwill.night-config:core:3.6.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "com.electronwill.night-config:toml:3.6.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.jline:jline:3.12.1",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.apache.maven:maven-artifact:3.6.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.jodah:typetools:0.6.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "java3d:vecmath:1.5.2"
},
{
"name": "org.apache.logging.log4j:log4j-api:2.11.2",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "org.apache.logging.log4j:log4j-core:2.11.2",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.minecrell:terminalconsoleappender:1.2.0",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "net.sf.jopt-simple:jopt-simple:5.0.4",
"url": "https://files.minecraftforge.net/maven/"
},
{
"name": "cpw.mods.forge:cursepacklocator:1.2.0",
"url": "https://files.minecraftforge.net/maven/"
}
],
"mainClass": "io.github.zekerzhayard.forgewrapper.Main",
"name": "MinecraftForge",
"requires": [
{
"equals": "1.14.4",
"uid": "net.minecraft"
}
],
"type": "release",
"uid": "net.minecraftforge",
"version": "1.14.4-28.1.109"
}

View file

@ -16,7 +16,7 @@ import io.github.zekerzhayard.forgewrapper.installer.Download;
public class Main {
public static void main(String[] args) throws Exception {
URL[] urls = Utils.getURLs(new ArrayList<>());
Pattern pattern = Pattern.compile("forge\\-(?<mcVersion>[0-9\\.]+)\\-(?<forgeVersion>[0-9\\.]+)\\-launcher\\.jar");
Pattern pattern = Pattern.compile("forge-(?<mcVersion>[0-9.]+)-(?<forgeVersion>[0-9.]+)\\.jar");
String version = "";
for (URL url : urls) {
Matcher matcher = pattern.matcher(url.getFile());
@ -30,6 +30,7 @@ public class Main {
URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {
Main.class.getProtectionDomain().getCodeSource().getLocation(),
Launcher.class.getProtectionDomain().getCodeSource().getLocation(),
new File(String.format("./.forgewrapper/forge-%s-installer.jar", version)).toURI().toURL()
}, null);

View file

@ -10,6 +10,8 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import cpw.mods.modlauncher.Launcher;
public class Utils {
public static URL[] getURLs(List<String> blackList) {
ClassLoader cl = Utils.class.getClassLoader();
@ -32,10 +34,14 @@ public class Utils {
return urls.toArray(new URL[0]);
}
public static File getLibrariesDir() throws URISyntaxException {
File wrapper = new File(Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI());
public static File getLibrariesDir() {
try {
File laucnher = new File(Launcher.class.getProtectionDomain().getCodeSource().getLocation().toURI());
// see https://github.com/MinecraftForge/MinecraftForge/blob/863ab2ca184cf2e2dfa134d07bfc20d6a9a6a4e8/src/main/java/net/minecraftforge/fml/relauncher/libraries/LibraryManager.java#L151
// /<version> /ForgeWrapper /ZekerZhayard /github /com /libraries
return wrapper.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
// /<version> /modlauncher /mods /cpw /libraries
return laucnher.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,132 @@
package io.github.zekerzhayard.forgewrapper.converter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class Converter {
public static void convert(Path installerPath, Path targetDir) throws Exception {
JsonObject installer = getInstallerJson(installerPath);
ArrayList<String> arguments = new ArrayList<>();
getElement(installer.getAsJsonObject("arguments"), "game").getAsJsonArray().iterator().forEachRemaining(je -> arguments.add(je.getAsString()));
String mcVersion = arguments.get(arguments.indexOf("--fml.mcVersion") + 1);
String forgeVersion = arguments.get(arguments.indexOf("--fml.forgeVersion") + 1);
String forgeFullVersion = "forge-" + mcVersion + "-" + forgeVersion;
StringBuilder wrapperVersion = new StringBuilder();
JsonObject pack = convertPackJson(mcVersion);
JsonObject patches = convertPatchesJson(installer, mcVersion, forgeVersion, wrapperVersion);
Files.createDirectories(targetDir);
// Copy mmc-pack.json and instance.cfg to <instance> folder.
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=" + forgeFullVersion).getBytes(StandardCharsets.UTF_8)), instancePath.resolve("instance.cfg"), StandardCopyOption.REPLACE_EXISTING);
// Copy ForgeWrapper to <instance>/libraries folder.
Path librariesPath = instancePath.resolve("libraries");
Files.createDirectories(librariesPath);
Files.copy(Paths.get(Converter.class.getProtectionDomain().getCodeSource().getLocation().toURI()), librariesPath.resolve(wrapperVersion.toString()), StandardCopyOption.REPLACE_EXISTING);
// Copy net.minecraftforge.json to <instance>/patches folder.
Path patchesPath = instancePath.resolve("patches");
Files.createDirectories(patchesPath);
Files.copy(new ByteArrayInputStream(patches.toString().getBytes(StandardCharsets.UTF_8)), patchesPath.resolve("net.minecraftforge.json"), StandardCopyOption.REPLACE_EXISTING);
// Copy forge installer to <instance>/.minecraft/.forgewrapper folder.
Path forgeWrapperPath = instancePath.resolve(".minecraft").resolve(".forgewrapper");
Files.createDirectories(forgeWrapperPath);
Files.copy(installerPath, forgeWrapperPath.resolve(forgeFullVersion + "-installer.jar"), StandardCopyOption.REPLACE_EXISTING);
}
private static JsonObject getInstallerJson(Path installerPath) {
try {
ZipFile zf = new ZipFile(installerPath.toFile());
ZipEntry versionFile = zf.getEntry("version.json");
if (versionFile == null) {
throw new RuntimeException("The forge installer is invalid!");
}
InputStreamReader isr = new InputStreamReader(zf.getInputStream(versionFile), StandardCharsets.UTF_8);
return new JsonParser().parse(isr).getAsJsonObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// Convert mmc-pack.json:
// - Replace Minecraft version
private static JsonObject convertPackJson(String mcVersion) {
JsonObject pack = new JsonParser().parse(new InputStreamReader(Converter.class.getResourceAsStream("/mmc-pack.json"))).getAsJsonObject();
for (JsonElement component : getElement(pack, "components").getAsJsonArray()) {
JsonObject componentObject = component.getAsJsonObject();
JsonElement version = getElement(componentObject, "version");
if (!version.isJsonNull() && getElement(componentObject, "uid").getAsString().equals("net.minecraft")) {
componentObject.addProperty("version", mcVersion);
}
}
return pack;
}
// Convert patches/net.minecraftforge.json:
// - Add libraries
// - Add forge-launcher url
// - Replace Minecraft & Forge versions
private static JsonObject convertPatchesJson(JsonObject installer, String mcVersion, String forgeVersion, StringBuilder wrapperVersion) {
JsonObject patches = new JsonParser().parse(new InputStreamReader(Converter.class.getResourceAsStream("/patches/net.minecraftforge.json"))).getAsJsonObject();
JsonArray libraries = getElement(patches, "libraries").getAsJsonArray();
for (JsonElement lib : libraries) {
String name = getElement(lib.getAsJsonObject(), "name").getAsString();
if (name.startsWith("io.github.zekerzhayard:ForgeWrapper:")) {
wrapperVersion.append(getElement(lib.getAsJsonObject(), "MMC-filename").getAsString());
}
}
for (JsonElement lib : getElement(installer ,"libraries").getAsJsonArray()) {
JsonObject artifact = getElement(getElement(lib.getAsJsonObject(), "downloads").getAsJsonObject(), "artifact").getAsJsonObject();
String path = getElement(artifact, "path").getAsString();
if (path.startsWith("net/minecraftforge/forge/")) {
artifact.getAsJsonObject().addProperty("url", "https://files.minecraftforge.net/maven/" + path.replace(".jar", "-launcher.jar"));
}
libraries.add(lib);
}
patches.addProperty("version", forgeVersion);
for (JsonElement require : getElement(patches, "requires").getAsJsonArray()) {
JsonObject requireObject = require.getAsJsonObject();
if (getElement(requireObject, "uid").getAsString().equals("net.minecraft")) {
requireObject.addProperty("equals", mcVersion);
}
}
return patches;
}
private 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;
}
}

View file

@ -0,0 +1,37 @@
package io.github.zekerzhayard.forgewrapper.converter;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
ArrayList<String> argsList = new ArrayList<>(Arrays.asList(args));
Path installer, instance;
try {
installer = Paths.get(argsList.get(argsList.indexOf("--installer") + 1));
instance = Paths.get(".");
if (argsList.contains("--instance")) {
instance = Paths.get(argsList.get(argsList.indexOf("--instance") + 1));
}
} catch (Exception e) {
System.out.println("Invalid arguments! Use: java -jar <ForgeWrapper.jar> [--installer] <forge-installer.jar> [--instance <instance-path>]");
throw new RuntimeException(e);
}
try {
URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {
Converter.class.getProtectionDomain().getCodeSource().getLocation(),
installer.toUri().toURL()
}, null);
ucl.loadClass("io.github.zekerzhayard.forgewrapper.converter.Converter").getMethod("convert", Path.class, Path.class).invoke(null, installer, instance);
System.out.println("Successfully install Forge for MultiMC!");
} catch (Exception e) {
System.out.println("Failed to install Forge!");
throw new RuntimeException(e);
}
}
}

View file

@ -1,7 +1,6 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.File;
import java.net.URISyntaxException;
import java.util.function.Predicate;
import io.github.zekerzhayard.forgewrapper.Utils;
@ -16,20 +15,19 @@ public class ClientInstall4MultiMC extends ClientInstall {
}
@Override
public boolean run(File target, Predicate<String> optionals) throws ActionCanceledException {
try {
public boolean run(File target, Predicate<String> optionals) {
File librariesDir = Utils.getLibrariesDir();
File clientTarget = new File(String.format("%s/com/mojang/minecraft/%s/minecraft-%s-client.jar", librariesDir.getAbsolutePath(), this.profile.getMinecraft(), this.profile.getMinecraft()));
if (!this.downloadLibraries(librariesDir, optionals)) {
boolean downloadLibraries = true; // Force true when without an internet connection.
try {
downloadLibraries = this.downloadLibraries(librariesDir, optionals);
} catch (ActionCanceledException e) {
e.printStackTrace();
}
if (!downloadLibraries) {
return false;
}
if (!this.processors.process(librariesDir, clientTarget)) {
return false;
}
} catch (URISyntaxException e) {
return false;
}
return true;
return this.processors.process(librariesDir, clientTarget);
}
}

View file

@ -16,11 +16,13 @@ public class Download {
localFile.getParentFile().mkdirs();
if (localFile.isFile()) {
try {
System.out.println("Checking Fingerprints of installer...");
Files.copy(new URL(url + ".md5").openConnection().getInputStream(), Paths.get(location + ".md5"), StandardCopyOption.REPLACE_EXISTING);
Files.copy(new URL(url + ".sha1").openConnection().getInputStream(), Paths.get(location + ".sha1"), StandardCopyOption.REPLACE_EXISTING);
String md5 = new String(Files.readAllBytes(Paths.get(location + ".md5")));
String sha1 = new String(Files.readAllBytes(Paths.get(location + ".sha1")));
if (!checkMD5(location, md5) || !checkSHA1(location, sha1)) {
System.out.println("Fingerprints do not match!");
localFile.delete();
}
} catch (IOException e) {
@ -31,6 +33,7 @@ public class Download {
if (localFile.isDirectory()) {
throw new RuntimeException(location + " must be a file!");
}
System.out.println("Downloading forge installer...");
Files.copy(new URL(url).openConnection().getInputStream(), Paths.get(location), StandardCopyOption.REPLACE_EXISTING);
download(url, location);
}

View file

@ -1,10 +1,5 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.regex.Pattern;
import net.minecraftforge.installer.actions.ActionCanceledException;
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
import net.minecraftforge.installer.json.Util;
@ -12,7 +7,7 @@ import net.minecraftforge.installer.json.Util;
public class Installer {
private static Install install;
public static boolean install() throws ActionCanceledException {
public static boolean install() {
ProgressCallback monitor = ProgressCallback.withOutputs(System.out);
install = Util.loadInstallProfile();
if (System.getProperty("java.net.preferIPv4Stack") == null) {
@ -37,34 +32,4 @@ public class Installer {
public static String getMcpVersion() {
return install.getData(true).get("MCP_VERSION").replace("'", "");
}
static void hookStdOut(ProgressCallback monitor) {
final Pattern endingWhitespace = Pattern.compile("\\r?\\n$");
final OutputStream monitorStream = new OutputStream() {
@Override
public void write(byte[] buf, int off, int len) {
byte[] toWrite = new byte[len];
System.arraycopy(buf, off, toWrite, 0, len);
write(toWrite);
}
@Override
public void write(byte[] b) {
String toWrite = new String(b);
toWrite = endingWhitespace.matcher(toWrite).replaceAll("");
if (!toWrite.isEmpty()) {
monitor.message(toWrite);
}
}
@Override
public void write(int b) {
write(new byte[] {(byte) b});
}
};
System.setOut(new PrintStream(monitorStream));
System.setErr(new PrintStream(monitorStream));
}
}

View file

@ -0,0 +1,13 @@
{
"formatVersion": 1,
"components": [
{
"important": true,
"uid": "net.minecraft",
"version": "{VERSION}"
},
{
"uid": "net.minecraftforge"
}
]
}

View file

@ -0,0 +1,21 @@
{
"formatVersion": 1,
"mainClass": "io.github.zekerzhayard.forgewrapper.Main",
"name": "Forge",
"requires": [
{
"equals": "{VERSION}",
"uid": "net.minecraft"
}
],
"type": "release",
"uid": "net.minecraftforge",
"version": "{FORGE_VERSION}",
"libraries": [
{
"name": "io.github.zekerzhayard:ForgeWrapper:${version}",
"MMC-hint": "local",
"MMC-filename": "ForgeWrapper-${version}.jar"
}
]
}