Use -Dfabric.addMods for adding mods in known sources

This commit is contained in:
Johannes Frohnmeyer 2021-12-25 22:09:51 +01:00
parent 320ffac699
commit 56e7b0194b
Signed by: Johannes
GPG Key ID: E76429612C2929F4
11 changed files with 149 additions and 80 deletions

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
.gradle/
.idea/
build/
run/
run*/
imgui.ini
hs_err_pid*
inceptum.log

View File

@ -32,7 +32,6 @@ public class WriteMetadataStep implements Step {
logs/
.mixin.out/
.fabric/
mods/*.jar
*.lock
eula.txt""");
}

View File

@ -8,6 +8,7 @@ import io.gitlab.jfronny.inceptum.model.inceptum.source.ModrinthModSource;
import io.gitlab.jfronny.inceptum.util.HashUtils;
import io.gitlab.jfronny.inceptum.util.api.CurseforgeApi;
import io.gitlab.jfronny.inceptum.util.api.ModrinthApi;
import org.eclipse.jgit.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
@ -21,53 +22,60 @@ public class ModDescription {
public List<String> dependents; // by file name
public List<String> dependencies; // by file name
public void hydrateUpdates(String gameVersion) {
public void initialize(String gameVersion) {
boolean modrinth = false;
boolean curseforge = false;
for (ModSource source : sources.keySet().toArray(ModSource[]::new)) {
if (source instanceof ModrinthModSource) modrinth = true;
if (source instanceof CurseforgeModSource) curseforge = true;
addSource(source, gameVersion);
}
}
public static ModDescription of(Path mod, String gameVersion) {
String sha1 = null;
String murmur2 = null;
try {
byte[] data = Files.readAllBytes(mod);
sha1 = HashUtils.sha1(data);
murmur2 = HashUtils.murmur2(data);
} catch (IOException e) {
Inceptum.LOGGER.error("Could not read file hash", e);
}
return of(sha1, murmur2, Optional.empty(), gameVersion);
}
public static ModDescription of(String sha1, String murmur2, Optional<ModSource> knownSource, String gameVersion) {
ModDescription res = new ModDescription();
res.sources = new LinkedHashMap<>();
res.sha1 = sha1;
res.murmur2 = murmur2;
res.dependents = new ArrayList<>();
res.dependencies = new ArrayList<>();
knownSource.ifPresent(modSource -> res.addSource(modSource, gameVersion));
if (knownSource.isEmpty() || !(knownSource.get() instanceof ModrinthModSource)) {
if (!modrinth) {
try {
res.addSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id), gameVersion);
addSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id), gameVersion);
}
catch (IOException e) {
// not found
}
}
if (knownSource.isEmpty() || !(knownSource.get() instanceof CurseforgeModSource)) {
if (!curseforge) {
try {
CurseforgeFingerprint cf = CurseforgeApi.checkFingerprint(murmur2);
if (!cf.exactMatches.isEmpty()) {
CurseforgeFingerprint.Mod f = cf.exactMatches.get(0);
res.addSource(new CurseforgeModSource(f.id, f.file.id), gameVersion);
addSource(new CurseforgeModSource(f.id, f.file.id), gameVersion);
}
}
catch (IOException e) {
// not found
}
}
}
public static ModDescription of(Path mod) {
ModDescription res = new ModDescription();
res.sources = new LinkedHashMap<>();
try {
byte[] data = Files.readAllBytes(mod);
res.sha1 = HashUtils.sha1(data);
res.murmur2 = HashUtils.murmur2(data);
} catch (IOException e) {
Inceptum.LOGGER.error("Could not read file hash", e);
}
res.dependents = new ArrayList<>();
res.dependencies = new ArrayList<>();
return res;
}
public static ModDescription of(String sha1, String murmur2, @Nullable ModSource knownSource, String gameVersion) {
ModDescription res = new ModDescription();
res.sources = new LinkedHashMap<>();
res.sha1 = sha1;
res.murmur2 = murmur2;
res.dependents = new ArrayList<>();
res.dependencies = new ArrayList<>();
if (knownSource != null) res.addSource(knownSource, gameVersion);
res.initialize(gameVersion);
return res;
}

View File

@ -28,8 +28,8 @@ public final class CurseforgeModSource implements ModSource {
}
@Override
public ModDownload download(Path modsDir) throws IOException {
Path path = modsDir.resolve(current.fileName);
public ModDownload download() throws IOException {
Path path = getJarPath();
Utils.downloadFile(current.downloadUrl, path);
byte[] data = Files.readAllBytes(path);
return new ModDownload(HashUtils.sha1(data), HashUtils.murmur2(data), path);
@ -65,7 +65,12 @@ public final class CurseforgeModSource implements ModSource {
@Override
public String getName() {
return "CurseForge/" + mod.name + '/' + current.displayName;
return "curseforge/" + (mod.slug == null ? mod.id : mod.slug) + '/' + current.id;
}
@Override
public String getFileName() {
return current.fileName;
}
@Override

View File

@ -15,8 +15,8 @@ public record DirectModSource(String fileName, String url, Set<ModSource> depend
}
@Override
public ModDownload download(Path modsDir) throws IOException {
Path p = modsDir.resolve(fileName);
public ModDownload download() throws IOException {
Path p = getJarPath();
Utils.downloadFile(url, p); //TODO test
byte[] data = Files.readAllBytes(p);
return new ModDownload(HashUtils.sha1(data), HashUtils.murmur2(data), p);
@ -39,7 +39,12 @@ public record DirectModSource(String fileName, String url, Set<ModSource> depend
@Override
public String getName() {
return "Direct";
return "direct/" + (url.contains(":") ? url.split(":")[1] : url).replaceAll("^/+", "");
}
@Override
public String getFileName() {
return fileName;
}
@Override

View File

@ -1,15 +1,21 @@
package io.gitlab.jfronny.inceptum.model.inceptum.source;
import io.gitlab.jfronny.inceptum.Inceptum;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
public interface ModSource {
ModDownload download(Path modsDir) throws IOException;
ModDownload download() throws IOException;
Set<ModSource> getDependencies() throws IOException;
Optional<ModSource> getUpdate(String gameVersion) throws IOException;
String getVersion();
String getName();
String getFileName();
boolean equals(ModSource other);
default Path getJarPath() {
return Inceptum.LIBRARIES_DIR.resolve("com").resolve(getName()).resolve(getFileName());
}
}

View File

@ -25,9 +25,9 @@ public final class ModrinthModSource implements ModSource {
}
@Override
public ModDownload download(Path modsDir) throws IOException {
public ModDownload download() throws IOException {
ModrinthVersion.File file = current.files.get(0);
Path path = modsDir.resolve(file.filename);
Path path = getJarPath();
Utils.downloadFile(file.url, file.hashes.sha1, path);
return new ModDownload(file.hashes.sha1, HashUtils.murmur2(Files.readAllBytes(path)), path);
}
@ -76,7 +76,12 @@ public final class ModrinthModSource implements ModSource {
@Override
public String getName() {
return "Modrinth/" + mod.title + '/' + current.name;
return "modrinth/" + (mod.slug == null ? mod.id : mod.slug) + '/' + current.version_number;
}
@Override
public String getFileName() {
return current.files.get(0).filename;
}
@Override

View File

@ -7,6 +7,7 @@ import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
@ -39,8 +40,7 @@ public class ModsDirScanner implements Closeable {
Set<IWModDescription> mods = new TreeSet<>();
if (Files.isDirectory(modsDir)) {
for (Path path : Utils.ls(modsDir)) {
if (!path.toString().endsWith(".imod"))
mods.add(get(path));
mods.add(get(path));
}
}
return mods;
@ -51,7 +51,7 @@ public class ModsDirScanner implements Closeable {
return descriptions.get(path);
else {
// not yet scanned
return new IWModDescription(path, getGameVersion());
return new IWModDescription(path); //TODO figure out why this causes blocking
}
}
@ -64,41 +64,31 @@ public class ModsDirScanner implements Closeable {
}
for (Path mods : Utils.ls(modsDir)) {
if (Files.isDirectory(mods)) {
descriptions.put(mods, new IWModDescription(mods, getGameVersion()));
descriptions.put(mods, new IWModDescription(mods));
} else {
if (mods.toString().endsWith(".jar") || mods.toString().endsWith(".jar.disabled")) {
Optional<Path> imod = Optional.of(mods.getParent().resolve(mods.getFileName() + ".imod"));
// fabric.mod.json
Optional<FabricModJson> fmj;
try (FileSystem fs = Utils.openZipFile(mods, false)) {
fmj = Files.exists(fs.getPath("fabric.mod.json"))
? Optional.of(Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class))
: Optional.empty();
}
// mod description
if (!Files.exists(imod.get())) {
Utils.writeObject(imod.get(), ModDescription.of(mods, getGameVersion()));
Utils.writeObject(imod.get(), ModDescription.of(mods));
}
Optional<ModDescription> md = Optional.of(Utils.loadObject(imod.get(), ModDescription.class));
md.get().hydrateUpdates(getGameVersion());
for (ModSource source : md.get().sources.keySet()) {
//Optional<ModSource> ms = source.getUpdate(instance.getMinecraftVersion());
//TODO properly cache
source.getUpdate(getGameVersion());
//if (ms.isEmpty()) continue;
//if (update.isEmpty()) update = ms;
}
descriptions.put(mods, new IWModDescription(mods, md, fmj, imod));
ModDescription md = Utils.loadObject(imod.get(), ModDescription.class);
evaluateSources(md, mods);
descriptions.put(imod.get(), new IWModDescription(mods, Optional.of(md), getFmj(mods, md), imod));
}
else if (mods.toString().endsWith(".imod")) {
// remove .imod if the corresponding jar doesn't exist
//TODO ensure this is not called while downloading a pack
//String fn = mods.getFileName().toString();
//if (!Files.exists(mods.getParent().resolve(fn.substring(0, fn.length() - 5))))
// Files.delete(mods);
String fn = mods.getFileName().toString();
if (!Files.exists(mods.getParent().resolve(fn.substring(0, fn.length() - 5))))
Files.delete(mods);
Path modFile = mods.getParent().resolve(fn.substring(0, fn.length() - 5));
ModDescription md = Utils.loadObject(mods, ModDescription.class);
evaluateSources(md, modFile);
descriptions.put(mods, new IWModDescription(mods, Optional.of(md), getFmj(modFile, md), Optional.of(mods)));
}
else {
descriptions.put(mods, new IWModDescription(mods, getGameVersion()));
descriptions.put(mods, new IWModDescription(mods));
}
}
}
@ -109,6 +99,37 @@ public class ModsDirScanner implements Closeable {
}
}
private void evaluateSources(ModDescription md, Path modFile) throws IOException {
md.initialize(getGameVersion());
boolean hasSource = false;
for (ModSource source : md.sources.keySet()) {
//Optional<ModSource> ms = source.getUpdate(instance.getMinecraftVersion());
//TODO properly cache
source.getUpdate(getGameVersion());
if (!Files.exists(source.getJarPath())) source.download();
hasSource = true;
//if (ms.isEmpty()) continue;
//if (update.isEmpty()) update = ms;
}
if (hasSource && Files.exists(modFile)) {
Files.delete(modFile);
}
}
private Optional<FabricModJson> getFmj(Path modJarDefault, ModDescription md) throws IOException, URISyntaxException {
if (!Files.exists(modJarDefault)) {
if (md.sources.isEmpty()) {
throw new FileNotFoundException("Mod " + modJarDefault.getFileName().toString() + " doesn't specify a source and has no file");
}
modJarDefault = List.copyOf(md.sources.keySet()).get(0).getJarPath();
}
try (FileSystem fs = Utils.openZipFile(modJarDefault, false)) {
return Files.exists(fs.getPath("fabric.mod.json"))
? Optional.of(Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class))
: Optional.empty();
}
}
@Override
public void close() {
disposed = true;
@ -147,8 +168,8 @@ public class ModsDirScanner implements Closeable {
* Generates a description with only the path and no additional information
* @param path The path of the mod entry
*/
public IWModDescription(Path path, String gameVersion) {
this(path, Files.isDirectory(path) ? Optional.empty() : Optional.of(ModDescription.of(path, gameVersion)), Optional.empty(), Optional.empty());
public IWModDescription(Path path) {
this(path, Files.isDirectory(path) ? Optional.empty() : Optional.of(ModDescription.of(path)), Optional.empty(), Optional.empty());
}
/**

View File

@ -100,10 +100,12 @@ public class Utils {
}
public static void downloadFile(String url, Path path) throws IOException {
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
Files.write(path, downloadData(url));
}
public static void downloadFile(String url, String sha1, Path path) throws IOException {
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
Files.write(path, downloadData(url, sha1));
}

View File

@ -4,6 +4,8 @@ import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.install.steps.DownloadLibrariesStep;
import io.gitlab.jfronny.inceptum.model.inceptum.ArtifactInfo;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import io.gitlab.jfronny.inceptum.model.mojang.MinecraftArgument;
import io.gitlab.jfronny.inceptum.model.mojang.VersionInfo;
import io.gitlab.jfronny.inceptum.model.mojang.VersionsListInfo;
@ -21,12 +23,10 @@ import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
public class InstanceLauncher {
public static void launch(Path instancePath, InstanceMeta instance, LaunchType launchType, boolean restart, AuthInfo authInfo) throws LaunchException, IOException {
@ -60,11 +60,29 @@ public class InstanceLauncher {
if (instance.minMem != null) args.add("-Xms" + instance.minMem);
if (instance.maxMem != null) args.add("-Xmx" + instance.maxMem);
if (instance.arguments != null && instance.arguments.jvm != null) args.addAll(instance.arguments.jvm);
// Native library path
args.add("-Djava.library.path=" + Inceptum.NATIVES_DIR.resolve(instance.getMinecraftVersion()).toAbsolutePath());
// Fabric imods
if (instance.isFabric()) {
StringBuilder fabricAddMods = new StringBuilder("-Dfabric.addMods=");
Path mods = instancePath.resolve("mods");
if (Files.exists(mods)) {
for (Path imod : Utils.ls(mods, ((Predicate<Path>) path -> path.getFileName().toString().endsWith(".imod")))) {
String fn = imod.getFileName().toString();
if (Files.exists(imod.getParent().resolve(fn.substring(0, fn.length() - 5))))
continue;
//TODO load imod from libraries via -Dfabric.addMods
Map<ModSource, Optional<ModSource>> sources = Utils.loadObject(imod, ModDescription.class).sources;
if (sources.isEmpty()) throw new LaunchException(".imod without attached jar contains no sources");
fabricAddMods.append(List.copyOf(sources.keySet()).get(0).getJarPath().toAbsolutePath());
fabricAddMods.append(File.pathSeparatorChar);
}
}
args.add(fabricAddMods.substring(0, fabricAddMods.length() - 1));
}
// Add classpath to args
args.add("-cp");
args.add(classPath.toString());
// Native library path
args.add("-Djava.library.path=" + Inceptum.NATIVES_DIR.resolve(instance.getMinecraftVersion()).toAbsolutePath());
// Main class
args.add(resolveMainClass(instance, versionInfo, gameJar, launchType));
// Game arguments

View File

@ -187,24 +187,24 @@ public class AddModWindow extends Window {
if (value.mod().isEmpty()) continue;
for (ModSource source : value.mod().get().sources.keySet()) {
if (ms.equals(source)) {
return new DownloadMeta(new ModDownload(value.mod().get().sha1, value.mod().get().murmur2, value.path()), value.mod().get(), source);
return new DownloadMeta(new ModDownload(value.mod().get().sha1, value.mod().get().murmur2, value.path()), value.mod().get(), source, modsDir);
}
}
}
ModDownload md = ms.download(modsDir);
ModDescription manifest = ModDescription.of(md.sha1(), md.murmur2(), Optional.of(ms), mds.getGameVersion());
ModDownload md = ms.download();
ModDescription manifest = ModDescription.of(md.sha1(), md.murmur2(), ms, mds.getGameVersion());
for (ModSource dependency : ms.getDependencies()) {
DownloadMeta depMan = download(dependency, modsDir, mds);
depMan.description.dependents.add(md.file().getFileName().toString());
manifest.dependencies.add(depMan.download.file().getFileName().toString());
depMan.write();
}
return new DownloadMeta(md, manifest, ms);
return new DownloadMeta(md, manifest, ms, modsDir);
}
public static record DownloadMeta(ModDownload download, ModDescription description, ModSource source) {
public static record DownloadMeta(ModDownload download, ModDescription description, ModSource source, Path modsDir) {
public void write() throws IOException {
Utils.writeObject(download.file().getParent().resolve(download.file().getFileName().toString() + ".imod"), description);
Utils.writeObject(modsDir.resolve(download.file().getFileName().toString() + ".imod"), description);
}
}
}