1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2025-02-16 01:41:22 +01:00

GH-481 Add Quilt loader support

... with a caveat that it may break in future due to Quilt's broken
metadata
This commit is contained in:
Henry Le Grys 2022-08-07 03:15:47 +01:00
parent 16f44a860c
commit 2a6c5ee015
6 changed files with 106 additions and 15 deletions

View File

@ -168,7 +168,9 @@ public class PackageBuilder {
processor = new ModernForgeLoaderProcessor();
}
} else if (BuilderUtils.getZipEntry(jarFile, "fabric-installer.json") != null) {
processor = new FabricLoaderProcessor();
processor = new FabricLoaderProcessor(FabricLoaderProcessor.Variant.FABRIC);
} else if (BuilderUtils.getZipEntry(jarFile, "quilt_installer.json") != null) {
processor = new FabricLoaderProcessor(FabricLoaderProcessor.Variant.QUILT);
}
} finally {
closer.close();
@ -205,8 +207,8 @@ public class PackageBuilder {
Files.createParentDirs(outputPath);
boolean found = false;
// Try just the URL, it might be a full URL to the file
if (!artifact.getUrl().isEmpty()) {
// If URL doesn't end with a /, it might be the direct file
if (!artifact.getUrl().endsWith("/")) {
found = tryDownloadLibrary(library, artifact, artifact.getUrl(), outputPath);
}
@ -261,7 +263,9 @@ public class PackageBuilder {
try {
log.info("Downloading library " + library.getName() + " from " + url + "...");
HttpRequest.get(url).execute().expectResponseCode(200).saveContent(tempFile);
HttpRequest.get(url).execute().expectResponseCode(200)
.expectContentType("application/java-archive", "application/octet-stream")
.saveContent(tempFile);
} catch (IOException e) {
log.info("Could not get file from " + url + ": " + e.getMessage());
return false;

View File

@ -4,10 +4,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Closer;
import com.skcraft.launcher.builder.BuilderUtils;
import com.skcraft.launcher.model.loader.FabricMod;
import com.skcraft.launcher.model.loader.QuiltMod;
import com.skcraft.launcher.model.loader.Versionable;
import com.skcraft.launcher.model.minecraft.Library;
import com.skcraft.launcher.model.minecraft.VersionManifest;
import com.skcraft.launcher.model.modpack.Manifest;
import com.skcraft.launcher.util.HttpRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import java.io.File;
@ -18,7 +21,10 @@ import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
@Log
@RequiredArgsConstructor
public class FabricLoaderProcessor implements ILoaderProcessor {
private final Variant variant;
@Override
public LoaderResult process(File loaderJar, Manifest manifest, ObjectMapper mapper, File baseDir) throws IOException {
JarFile jarFile = new JarFile(loaderJar);
@ -26,24 +32,23 @@ public class FabricLoaderProcessor implements ILoaderProcessor {
Closer closer = Closer.create();
try {
String loaderVersion;
Versionable loaderMod;
ZipEntry modEntry = BuilderUtils.getZipEntry(jarFile, "fabric.mod.json");
ZipEntry modEntry = BuilderUtils.getZipEntry(jarFile, variant.modJsonName);
if (modEntry != null) {
InputStreamReader reader = new InputStreamReader(jarFile.getInputStream(modEntry));
FabricMod loaderMod = mapper.readValue(
BuilderUtils.readStringFromStream(closer.register(reader)), FabricMod.class);
loaderVersion = loaderMod.getVersion();
loaderMod = mapper.readValue(
BuilderUtils.readStringFromStream(closer.register(reader)), variant.mappedClass);
} else {
log.warning("Fabric loader has no 'fabric.mod.json' file, is it really a Fabric Loader jar?");
log.warning(String.format("%s loader has no '%s' file, is it really a %s Loader jar?",
variant.friendlyName, variant.modJsonName, variant.friendlyName));
return null;
}
log.info("Downloading fabric metadata...");
log.info(String.format("Downloading %s metadata...", variant.friendlyName));
URL metaUrl = HttpRequest.url(
String.format("https://meta.fabricmc.net/v2/versions/loader/%s/%s/profile/json",
manifest.getGameVersion(), loaderVersion));
String.format(variant.metaUrl, manifest.getGameVersion(), loaderMod.getVersion()));
VersionManifest fabricManifest = HttpRequest.get(metaUrl)
.execute()
.expectResponseCode(200)
@ -51,6 +56,20 @@ public class FabricLoaderProcessor implements ILoaderProcessor {
.asJson(VersionManifest.class);
for (Library library : fabricManifest.getLibraries()) {
// To quote a famous comment: "And here we come upon a sad state of affairs."
// Quilt's meta API returns broken data about how to launch the game. It specifies its own incomplete
// and ultimately broken set of intermediary mappings called "hashed". If the loader finds "hashed" in
// the classpath it tries to use it and blows up because it doesn't work.
// We work around this here by just throwing the hashed library out - they do now at least specify
// fabric's intermediary mappings in the library list, which DO work.
// Historical note: previously they didn't do this! Every launcher that added Quilt support had to add
// a hack that replaced hashed with intermediary! This is a lot of technical debt that is gonna come
// back to bite them in the ass later, because it's all still there!
// TODO pester Quilt again about fixing this....
if (library.getName().startsWith("org.quiltmc:hashed") && loaderMod instanceof QuiltMod) {
continue;
}
result.getLoaderLibraries().add(library);
log.info("Adding loader library " + library.getName());
}
@ -61,7 +80,8 @@ public class FabricLoaderProcessor implements ILoaderProcessor {
log.info("Using main class " + mainClass);
}
} catch (InterruptedException e) {
log.warning("HTTP request to fabric metadata API was interrupted, this will probably not work!");
log.warning(String.format("HTTP request to %s metadata API was interrupted!", variant.friendlyName));
throw new IOException(e);
} finally {
closer.close();
jarFile.close();
@ -69,4 +89,15 @@ public class FabricLoaderProcessor implements ILoaderProcessor {
return result;
}
@RequiredArgsConstructor
public enum Variant {
FABRIC("Fabric", "fabric.mod.json", "https://meta.fabricmc.net/v2/versions/loader/%s/%s/profile/json", FabricMod.class),
QUILT("Quilt", "quilt.mod.json", "https://meta.quiltmc.org/v3/versions/loader/%s/%s/profile/json", QuiltMod.class);
private final String friendlyName;
private final String modJsonName;
private final String metaUrl;
private final Class<? extends Versionable> mappedClass;
}
}

View File

@ -5,7 +5,7 @@ import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FabricMod {
public class FabricMod implements Versionable {
private String id;
private String name;
private String version;

View File

@ -0,0 +1,29 @@
package com.skcraft.launcher.model.loader;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class QuiltMod implements Versionable {
@JsonProperty("schema_version")
private int schemaVersion;
@JsonProperty("quilt_loader")
private Mod meta;
@Override
public String getVersion() {
return meta.getVersion();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Mod {
private String version;
@JsonProperty("intermediate_mappings")
private String intermediateMappings;
}
}

View File

@ -0,0 +1,5 @@
package com.skcraft.launcher.model.loader;
public interface Versionable {
String getVersion();
}

View File

@ -232,6 +232,28 @@ public class HttpRequest implements Closeable, ProgressObservable {
throw exc;
}
/**
* Continue if the content type matches, otherwise throw an exception
*
* @param expectedTypes Expected content-type(s)
* @return this object
* @throws IOException Unexpected content-type or other error
*/
public HttpRequest expectContentType(String... expectedTypes) throws IOException {
if (conn == null) throw new IllegalArgumentException("No connection has been made!");
String contentType = conn.getHeaderField("Content-Type");
for (String expectedType : expectedTypes) {
if (expectedType.equals(contentType)) {
return this;
}
}
close();
throw new IOException(String.format("Did not get expected content type '%s', instead got '%s'.",
String.join(" | ", expectedTypes), contentType));
}
/**
* Get the response code.
*