Skip to content

Commit

Permalink
Add --cursepack argument for installing curseforge modpack.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZekerZhayard committed Mar 20, 2020
1 parent 7114b3e commit bd7373a
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 41 deletions.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ 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/].
### 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:
```
java -jar <ForgeWrapper.jar> [--installer] <forge-installer.jar> [--instance <instance-path>]
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>`.
5. Run MultiMC, and you will see a new instance named `forge-<mcVersion>-<forgeVersion>`.

### 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 <ForgeWrapper.jar> --cursepack=<curseforge-modpack.zip> [--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 `<modpackName>-<modpackVersion>`.
*Notice: CurseForge modpack will be installed on first launch by [cursepacklocator](https://github.com/cpw/cursepacklocator), it will take a few minutes.*
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ apply plugin: "idea"

sourceCompatibility = targetCompatibility = 1.8

version = "1.1.0"
version = "1.2.0"
group = "io.github.zekerzhayard"
archivesBaseName = rootProject.name

Expand All @@ -19,7 +19,6 @@ repositories {
}

dependencies {
compile "commons-codec:commons-codec:1.10"
compile "cpw.mods:modlauncher:4.1.0"
compile "net.minecraftforge:forge:1.14.4-28.2.0:installer"
}
Expand Down
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Thu Mar 12 20:06:29 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
166 changes: 166 additions & 0 deletions src/main/java/cpw/mods/forge/cursepacklocator/Murmur2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
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.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -21,26 +21,32 @@
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.zekerzhayard.forgewrapper.installer.Download;

public class Converter {
public static void convert(Path installerPath, Path targetDir) throws Exception {
JsonObject installer = getInstallerJson(installerPath);
public static void convert(Path installerPath, Path targetDir, String cursepack) throws Exception {
if (cursepack != null) {
installerPath = getForgeInstallerFromCursePack(cursepack);
}

JsonObject installer = getJsonFromZip(installerPath, "version.json");
List<String> 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, mcVersion, forgeVersion, wrapperVersion);
JsonObject patches = convertPatchesJson(installer, mcVersion, forgeVersion, wrapperVersion, cursepack);

Files.createDirectories(targetDir);

// Copy mmc-pack.json and instance.cfg to <instance> folder.
Path instancePath = targetDir.resolve(forgeFullVersion);
Path instancePath = targetDir.resolve(instanceName);
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);
Files.copy(new ByteArrayInputStream(("InstanceType=OneSix\nname=" + instanceName).getBytes(StandardCharsets.UTF_8)), instancePath.resolve("instance.cfg"), StandardCopyOption.REPLACE_EXISTING);

// Copy ForgeWrapper to <instance>/libraries folder.
Path librariesPath = instancePath.resolve("libraries");
Expand All @@ -56,10 +62,22 @@ public static void convert(Path installerPath, Path targetDir) throws Exception
Path forgeWrapperPath = instancePath.resolve(".minecraft").resolve(".forgewrapper");
Files.createDirectories(forgeWrapperPath);
Files.copy(installerPath, forgeWrapperPath.resolve(forgeFullVersion + "-installer.jar"), StandardCopyOption.REPLACE_EXISTING);

// Extract all curse pack entries to <instance>/.minecraft folder.
if (cursepack != null) {
ZipFile zip = new ZipFile(cursepack);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
Path targetFolder = forgeWrapperPath.getParent().resolve(entry.getName());
Files.createDirectories(targetFolder.getParent());
Files.copy(zip.getInputStream(entry), targetFolder, StandardCopyOption.REPLACE_EXISTING);
}
}
}

public static List<String> getAdditionalArgs(Path installerPath) {
JsonObject installer = getInstallerJson(installerPath);
JsonObject installer = getJsonFromZip(installerPath, "version.json");
return getAdditionalArgs(installer);
}

Expand All @@ -69,12 +87,12 @@ public static List<String> getAdditionalArgs(JsonObject installer) {
return args;
}

public static JsonObject getInstallerJson(Path installerPath) {
public static JsonObject getJsonFromZip(Path path, String json) {
try {
ZipFile zf = new ZipFile(installerPath.toFile());
ZipEntry versionFile = zf.getEntry("version.json");
ZipFile zf = new ZipFile(path.toFile());
ZipEntry versionFile = zf.getEntry(json);
if (versionFile == null) {
throw new RuntimeException("The forge installer is invalid!");
throw new RuntimeException("The zip file is invalid!");
}
InputStreamReader isr = new InputStreamReader(zf.getInputStream(versionFile), StandardCharsets.UTF_8);
return new JsonParser().parse(isr).getAsJsonObject();
Expand All @@ -83,6 +101,28 @@ public static JsonObject getInstallerJson(Path installerPath) {
}
}

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));
Download.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) {
Expand All @@ -102,7 +142,7 @@ private static JsonObject convertPackJson(String mcVersion) {
// - Add libraries
// - Add forge-launcher url
// - Replace Minecraft & Forge versions
private static JsonObject convertPatchesJson(JsonObject installer, String mcVersion, String forgeVersion, StringBuilder wrapperVersion) {
private static JsonObject convertPatchesJson(JsonObject installer, String mcVersion, String forgeVersion, StringBuilder wrapperVersion, String cursepack) {
JsonObject patches = new JsonParser().parse(new InputStreamReader(Converter.class.getResourceAsStream("/patches/net.minecraftforge.json"))).getAsJsonObject();
JsonArray libraries = getElement(patches, "libraries").getAsJsonArray();

Expand All @@ -112,10 +152,16 @@ private static JsonObject convertPatchesJson(JsonObject installer, String mcVers
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);
}
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/")) {
if (path.equals(String.format("net/minecraftforge/forge/%s-%s/forge-%s-%s.jar", mcVersion, forgeVersion, mcVersion, forgeVersion))) {
artifact.getAsJsonObject().addProperty("url", "https://files.minecraftforge.net/maven/" + path.replace(".jar", "-launcher.jar"));
}
libraries.add(lib);
Expand Down
Loading

0 comments on commit bd7373a

Please sign in to comment.