Rework exporting

This commit is contained in:
Johannes Frohnmeyer 2022-09-18 15:15:30 +02:00
parent da621e0eba
commit f7a3d5be53
Signed by: Johannes
GPG Key ID: E76429612C2929F4
22 changed files with 446 additions and 259 deletions

View File

@ -28,7 +28,7 @@ println("Using Inceptum Build Script $version")
val lwjglVersion by extra("3.3.1")
val imguiVersion by extra("1.86.4")
val jfCommonsVersion by extra("2022.9.6+19-13-24")
val jfCommonsVersion by extra("2022.9.18+12-38-21")
val jgitVersion by extra("6.2.0.202206071550-r")
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
if (flavorProp != "custom" && flavorProp != "maven" && flavorProp != "fat" && flavorProp != "windows" && flavorProp != "linux" && flavorProp != "macos")

View File

@ -9,7 +9,7 @@ public class MetaHolder {
static {
if (System.getProperty("inceptum.base") == null) {
Path runDir = Path.of(".").resolve("run").toAbsolutePath();
Path runDir = getPath("run");
if (!BuildMetadata.IS_RELEASE) BASE_PATH = runDir;
else if (Files.exists(runDir)) BASE_PATH = runDir;
else {
@ -43,7 +43,7 @@ public class MetaHolder {
private static boolean isWrapper = false;
private static Path getPath(String text) {
return Paths.get(text).toAbsolutePath();
return Paths.get(text).toAbsolutePath().normalize();
}
public static void setWrapperFlag() {

View File

@ -3,9 +3,9 @@ package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.R;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.export.InstanceExporter;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporters;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -15,6 +15,7 @@ import java.util.List;
public class ExportCommand extends BaseInstanceCommand {
public ExportCommand() {
this(List.of("export"), List.of(
new ExportCommand(List.of("curseforge", "cf"), List.of()),
new MultiMCExportCommand(List.of("multimc", "mmc"), List.of())
));
}
@ -31,7 +32,7 @@ public class ExportCommand extends BaseInstanceCommand {
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
InstanceExporter.exportCurseZip(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
Exporters.CURSE_FORGE.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
}
private static class MultiMCExportCommand extends BaseInstanceCommand {
@ -46,7 +47,7 @@ public class ExportCommand extends BaseInstanceCommand {
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
InstanceExporter.exportMultiMCZip(state, instancePath, meta, mds, Paths.get(args.get(0)));
Exporters.MULTI_MC.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), "1.0");
}
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.inceptum.imgui;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.imgui.window.MicrosoftLoginWindow;
import io.gitlab.jfronny.inceptum.imgui.window.dialog.AlertWindow;
@ -20,7 +21,7 @@ public class GuiEnvBackend implements LauncherEnv.EnvBackend {
@Override
public void showError(String message, Throwable t) {
Utils.LOGGER.error(message, t);
GuiMain.WINDOWS.add(new AlertWindow(message, t.toString()));
GuiMain.WINDOWS.add(new AlertWindow(message, StringFormatter.toString(t)));
}
@Override

View File

@ -179,8 +179,7 @@ public class GuiMain {
io.addConfigFlags(ImGuiConfigFlags.ViewportsEnable);
io.setConfigViewportsNoAutoMerge(true);
try (InputStream is = LauncherEnv.class.getClassLoader().getResourceAsStream("font.ttf")) {
assert is != null;
io.setFontDefault(io.getFonts().addFontFromMemoryTTF(is.readAllBytes(), 16f));
io.setFontDefault(io.getFonts().addFontFromMemoryTTF(Objects.requireNonNull(is).readAllBytes(), 16f));
} catch (IOException e) {
Utils.LOGGER.error("Could not load font", e);
}
@ -202,8 +201,15 @@ public class GuiMain {
for (Window window : WINDOWS.toArray(new Window[0])) {
if (window.isNew()) window.preFirstDraw();
String title = window.getName() + "##" + System.identityHashCode(window);
if (ImGui.begin(title, window.getOpenState(), window.getFlags()))
window.draw();
if (window.isCloseable()) {
if (ImGui.begin(title, window.getOpenState(), window.getFlags())) {
window.draw();
}
} else {
if (ImGui.begin(title, window.getFlags())) {
window.draw();
}
}
ImGui.end();
if (!window.getOpenState().get() && !window.isClosed()) window.close();
}

View File

@ -51,6 +51,10 @@ public abstract class Window implements Closeable {
return openState;
}
public boolean isCloseable() {
return true;
}
public enum State {
New, Open, Closed
}

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.inceptum.imgui.window.dialog;
import imgui.ImGui;
import imgui.flag.ImGuiWindowFlags;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.imgui.window.Window;
@ -13,7 +14,6 @@ public class ProcessStateWatcherWindow extends Window {
private final ProcessState state;
private final Runnable cancel;
private final AtomicBoolean canceled = new AtomicBoolean(false);
private boolean finished;
public ProcessStateWatcherWindow(String title, String errorMessage, ProcessState state, ThrowingConsumer<AtomicBoolean, ?> executor, @Nullable Runnable cancel) {
super(title);
@ -22,12 +22,12 @@ public class ProcessStateWatcherWindow extends Window {
new Thread(() -> {
try {
executor.accept(canceled);
if (canceled.get() && cancel != null) cancel.run();
finished = true;
} catch (Throwable e) {
canceled.set(true);
LauncherEnv.showError(errorMessage, e);
} finally {
close();
}
close();
}).start();
}
@ -41,15 +41,18 @@ public class ProcessStateWatcherWindow extends Window {
ImGui.textUnformatted(state.getCurrentStep());
if (cancel != null && ImGui.button("Cancel")) {
canceled.set(true);
close();
}
}
@Override
public void close() {
if (finished) {
super.close();
} else if (cancel != null) {
canceled.set(true);
}
super.close();
if (canceled.get() && cancel != null) cancel.run();
}
@Override
public boolean isCloseable() {
return false;
}
}

View File

@ -1,14 +1,14 @@
package io.gitlab.jfronny.inceptum.imgui.window.edit;
import imgui.ImGui;
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
import io.gitlab.jfronny.inceptum.common.R;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.imgui.GuiMain;
import io.gitlab.jfronny.inceptum.imgui.control.Tab;
import io.gitlab.jfronny.inceptum.imgui.window.dialog.ProcessStateWatcherWindow;
import io.gitlab.jfronny.inceptum.imgui.window.dialog.TextBoxWindow;
import io.gitlab.jfronny.inceptum.launcher.system.export.InstanceExporter;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporter;
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporters;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
@ -16,8 +16,6 @@ import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
public class ExportTab extends Tab {
private final InstanceEditWindow window;
@ -30,41 +28,34 @@ public class ExportTab extends Tab {
@Override
protected void renderInner() {
if (window.mds.isComplete()) {
AtomicReference<String> cfVersion = new AtomicReference<>();
exportVariant("CurseForge", "zip", it -> {
GuiMain.WINDOWS.add(new TextBoxWindow("Version", "Please enter the version to list in the modpack file", "1.0", version -> {
cfVersion.set(version);
it.run();
}, R::nop));
}, (state, exportPath) -> {
InstanceExporter.exportCurseZip(state, window.path, window.instance, window.mds, exportPath, cfVersion.get());
});
exportVariant("MultiMC", "zip", Runnable::run, (state, exportPath) -> {
InstanceExporter.exportMultiMCZip(state, window.path, window.instance, window.mds, exportPath);
});
for (Exporter<?> exporter : Exporters.EXPORTERS) {
if (ImGui.button(exporter.getName())) {
GuiMain.open(new TextBoxWindow("Version", "Please enter the current version of your modpack", "1.0", version -> {
String defaultName = window.name + " " + version + " (" + exporter.getName() + ")." + exporter.getFileExtension();
String filter = "*." + exporter.getFileExtension();
String file = saveFileDialog("Export " + exporter.getName() + " Pack", defaultName, filter, exporter.getName() + " packs (" + filter + ")");
if (file != null) {
ProcessState state = new ProcessState(Exporters.STEP_COUNT, "Initializing...");
GuiMain.open(new ProcessStateWatcherWindow("Exporting", "Could not export pack", state, cToken -> {
Path exportPath = Paths.get(file);
exporter.generate(state, window.path, window.instance, window.mds, exportPath, version);
LauncherEnv.showInfo(window.name + " has been successfully exported to " + exportPath, "Successfully exported");
}, null));
}
}, R::nop));
}
}
} else {
ImGui.text("The mods directory scan must be completed.\nThe progress for this can be observed in the mods tab");
}
}
private <T extends Throwable> void exportVariant(String name, String packExt, Consumer<Runnable> beforeSave, ThrowingBiConsumer<ProcessState, Path, T> export) {
if (ImGui.button(name)) {
beforeSave.accept(() -> {
try (MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer aFilterPatterns = stack.mallocPointer(2);
aFilterPatterns.put(stack.UTF8("*." + packExt));
aFilterPatterns.flip();
String p = TinyFileDialogs.tinyfd_saveFileDialog("Export Pack", "", aFilterPatterns, name + " packs (*." + packExt + ")");
if (p != null) {
ProcessState state = new ProcessState(InstanceExporter.STEP_COUNT, "Initializing...");
GuiMain.open(new ProcessStateWatcherWindow("Exporting", "Could not export pack", state, cToken -> {
Path exportPath = Paths.get(p);
export.accept(state, exportPath);
LauncherEnv.showInfo(window.path.getFileName().toString() + " has been successfully exported to " + exportPath, "Successfully exported");
}, null));
}
}
});
private String saveFileDialog(String title, String defaultName, String filter, String filterDescription) {
try (MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer filterPatterns = stack.mallocPointer(2);
filterPatterns.put(stack.UTF8(filter));
filterPatterns.flip();
return TinyFileDialogs.tinyfd_saveFileDialog(title, defaultName, filterPatterns, filterDescription);
}
}
}

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class CleanupFileVisitor implements FileVisitor<Path> {
@Override
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes) throws IOException {
if (JFiles.list(path).isEmpty()) {
Files.delete(path);
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path path, IOException e) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path path, IOException e) {
return FileVisitResult.CONTINUE;
}
}

View File

@ -0,0 +1,73 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.InceptumConfig;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeModpackManifest;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.source.CurseforgeModSource;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
public class CurseForgeExporter extends Exporter<CurseforgeModpackManifest> {
private static final String OVERRIDES_DIR_DEFAULT = "overrides";
public CurseForgeExporter() {
super("CurseForge", "zip", "overrides");
}
@Override
protected CurseforgeModpackManifest generateManifests(Path root, InstanceMeta instance, String name, String version) throws IOException {
CurseforgeModpackManifest manifest = new CurseforgeModpackManifest();
manifest.minecraft = new CurseforgeModpackManifest.Minecraft();
manifest.minecraft.version = instance.getMinecraftVersion();
manifest.manifestType = "minecraftModpack";
manifest.manifestVersion = 1;
manifest.name = name;
manifest.version = version;
manifest.author = InceptumConfig.authorName;
manifest.overrides = OVERRIDES_DIR_DEFAULT;
manifest.minecraft.modLoaders = new LinkedHashSet<>();
if (instance.isFabric()) {
CurseforgeModpackManifest.Minecraft.ModLoader loader = new CurseforgeModpackManifest.Minecraft.ModLoader();
loader.id = "fabric-" + instance.getLoaderVersion();
loader.primary = true;
manifest.minecraft.modLoaders.add(loader);
}
JFiles.writeObject(root.resolve("manifest.json"), manifest);
return manifest;
}
@Override
protected void addMods(Path root, InstanceMeta instance, Iterable<IWModDescription> mods, CurseforgeModpackManifest manifest, Path modsOverrides) throws IOException {
modsLoop: for(IWModDescription mod : mods) {
if (ModPath.isImod(mod.path())) {
Set<ModSource> sources = mod.mod().orElseThrow().sources.keySet();
for (ModSource source : sources) {
if (source instanceof CurseforgeModSource cms) {
manifest.files.add(cms.toManifest());
continue modsLoop;
}
}
// Not available on CF
for (ModSource source : sources) {
Files.createDirectories(modsOverrides);
Files.copy(source.getJarPath(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
} else {
Files.createDirectories(modsOverrides);
Files.copy(mod.path(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
throw new FileNotFoundException("Could not find mod file for " + mod.path());
}
JFiles.writeObject(root.resolve("manifest.json"), manifest);
}
}

View File

@ -0,0 +1,78 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.launcher.util.*;
import io.gitlab.jfronny.inceptum.launcher.util.ignore.IgnoringWalk;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
public abstract class Exporter<Manifest> {
private final String name;
private final String fileExtension;
private final String overridesDirName;
public Exporter(String name, String fileExtension, String overridesDirName) {
this.name = Objects.requireNonNull(name);
this.fileExtension = Objects.requireNonNull(fileExtension);
this.overridesDirName = Objects.requireNonNull(overridesDirName);
}
protected abstract Manifest generateManifests(Path root, InstanceMeta instance, String name, String version) throws IOException;
protected abstract void addMods(Path root, InstanceMeta instance, Iterable<IWModDescription> mods, Manifest manifest, Path modsOverrides) throws IOException;
public void generate(ProcessState state, Path instanceDir, InstanceMeta meta, ModsDirScanner mds, Path exportPath, String version) throws IOException {
if (Files.exists(exportPath)) Files.delete(exportPath);
try (FileSystem fs = Utils.openZipFile(exportPath, true)) {
Path root = fs.getPath(".");
Path overrides = fs.getPath(overridesDirName);
state.incrementStep("Preparing manifests");
Manifest manifest = generateManifests(root, meta, instanceDir.getFileName().toString(), version);
if (meta.isFabric()) {
state.incrementStep("Adding mods");
if (!mds.isComplete()) throw new IOException("Mods dir scan is not yet completed");
addMods(root, meta, new StreamIterable<>(mds.getMods().stream().filter(mod -> {
if (!ModPath.isEnabled(mod.path())) return false;
state.updateStep(mod.path().toString());
return true;
})), manifest, overrides.resolve("mods"));
}
state.incrementStep("Adding files");
filesLoop: for (Path path : new StreamIterable<>(IgnoringWalk.walk(instanceDir))) {
Path relativePath = instanceDir.relativize(path).normalize();
Path target = overrides;
for (Path segment : relativePath) {
if (target == overrides && segment.toString().equals("mods")) continue filesLoop;
if (segment.toString().startsWith(".")) continue filesLoop;
target = target.resolve(segment.toString());
}
state.updateStep(relativePath.toString());
Files.createDirectories(target.getParent());
Files.copy(path, target);
}
state.incrementStep("Cleaning up");
Files.walkFileTree(root, new CleanupFileVisitor());
} catch (URISyntaxException se) {
// Can only be thrown by openZipFile, the target file therefore cannot exist
throw new IOException("Could not open export path", se);
} catch (Throwable t) {
if (Files.exists(exportPath)) Files.delete(exportPath);
throw t;
}
}
public String getName() {
return name;
}
public String getFileExtension() {
return fileExtension;
}
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import java.util.List;
//TODO modrinth export
public class Exporters {
public static final int STEP_COUNT = 4;
public static final CurseForgeExporter CURSE_FORGE = new CurseForgeExporter();
public static final MultiMCExporter MULTI_MC = new MultiMCExporter();
public static final List<Exporter<?>> EXPORTERS = List.of(CURSE_FORGE, MULTI_MC);
}

View File

@ -1,188 +0,0 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.InceptumConfig;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeModpackManifest;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.model.multimc.MMCPackMeta;
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import io.gitlab.jfronny.inceptum.launcher.util.ignore.IgnoringWalk;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.launcher.system.source.CurseforgeModSource;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.time.Instant;
import java.util.*;
//TODO deduplicate
//TODO modrinth export
public class InstanceExporter {
public static final int STEP_COUNT = 3;
private static final String OVERRIDES_DIR_DEFAULT = "overrides";
private static final String DOT_MINECRAFT = ".minecraft";
public static void exportCurseZip(ProcessState state, Path instanceDir, InstanceMeta meta, ModsDirScanner mds, Path exportPath, String version) throws IOException, URISyntaxException {
if (Files.exists(exportPath)) Files.delete(exportPath);
try (FileSystem fs = Utils.openZipFile(exportPath, true)) {
state.incrementStep("Preparing basic manifest");
CurseforgeModpackManifest manifest = new CurseforgeModpackManifest();
manifest.minecraft = new CurseforgeModpackManifest.Minecraft();
manifest.minecraft.version = meta.getMinecraftVersion();
manifest.minecraft.modLoaders = new LinkedHashSet<>();
if (meta.isFabric()) {
CurseforgeModpackManifest.Minecraft.ModLoader loader = new CurseforgeModpackManifest.Minecraft.ModLoader();
loader.id = "fabric-" + meta.getLoaderVersion();
loader.primary = true;
manifest.minecraft.modLoaders.add(loader);
}
manifest.manifestType = "minecraftModpack";
manifest.manifestVersion = 1;
manifest.name = instanceDir.getFileName().toString();
manifest.version = version;
manifest.author = InceptumConfig.authorName;
manifest.overrides = OVERRIDES_DIR_DEFAULT;
Path overrides = fs.getPath(OVERRIDES_DIR_DEFAULT);
Files.createDirectories(overrides);
if (meta.isFabric()) {
state.incrementStep("Adding mods");
manifest.files = new LinkedHashSet<>();
if (!mds.isComplete()) throw new IOException("Mods dir scan is not yet completed");
modsLoop:
for (IWModDescription mod : mds.getMods()) {
if (!ModPath.isEnabled(mod.path())) continue;
state.updateStep(mod.path().toString());
if (ModPath.isImod(mod.path())) {
Set<ModSource> sources = mod.mod().orElseThrow().sources.keySet();
for (ModSource source : sources) {
if (source instanceof CurseforgeModSource cms) {
CurseforgeModpackManifest.File f = new CurseforgeModpackManifest.File();
f.projectID = cms.getProjectId();
f.fileID = cms.getFileId();
f.required = true;
manifest.files.add(f);
continue modsLoop;
}
}
// Not available on CF
for (ModSource source : sources) {
Path jarPath = source.getJarPath();
Files.copy(jarPath, jarPath.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
}
Path md = overrides.resolve("mods");
Files.createDirectories(md);
Files.copy(mod.path(), md.resolve(mod.path().getFileName().toString()));
}
}
state.incrementStep("Adding files");
for (Path l : IgnoringWalk.walk(instanceDir).toList()) {
String fn = l.getFileName().toString();
if (fn.equals("mods") || fn.startsWith(".")) continue;
state.updateStep(fn);
Path target = overrides.resolve(fn);
JFiles.copyContent(l, target);
}
JFiles.writeObject(fs.getPath("manifest.json"), manifest);
}
}
public static void exportMultiMCZip(ProcessState state, Path instanceDir, InstanceMeta meta, ModsDirScanner mds, Path exportPath) throws IOException, URISyntaxException {
if (Files.exists(exportPath)) Files.delete(exportPath);
try (FileSystem fs = Utils.openZipFile(exportPath, true)) {
{
state.incrementStep("Preparing basic manifest");
MMCPackMeta manifest = new MMCPackMeta();
manifest.formatVersion = 1;
manifest.components = new ArrayList<>();
MMCPackMeta.Component lwjgl = new MMCPackMeta.Component();
lwjgl.dependencyOnly = true;
lwjgl.uid = "org.lwjgl3";
lwjgl.version = "3.2.2"; //TODO get automatically
manifest.components.add(lwjgl);
MMCPackMeta.Component minecraft = new MMCPackMeta.Component();
minecraft.important = true;
minecraft.uid = "net.minecraft";
minecraft.version = meta.getMinecraftVersion();
manifest.components.add(minecraft);
if (meta.isFabric()) {
MMCPackMeta.Component intermediary = new MMCPackMeta.Component();
intermediary.dependencyOnly = true;
intermediary.uid = "net.fabricmc.intermediary";
intermediary.version = meta.getMinecraftVersion();
manifest.components.add(intermediary);
MMCPackMeta.Component fabric = new MMCPackMeta.Component();
fabric.uid = "net.fabricmc.fabric-loader";
fabric.version = meta.getLoaderVersion();
manifest.components.add(fabric);
}
JFiles.writeObject(fs.getPath("mmc-pack.json"), manifest);
}
{
state.updateStep("Preparing instance config");
Files.writeString(fs.getPath("instance.cfg"), String.format("""
ForgeVersion=
InstanceType=OneSix
IntendedVersion=
JoinServerOnLaunch=false
LWJGLVersion=
LiteloaderVersion=
LogPrePostOutput=true
MCLaunchMethod=LauncherPart
OverrideCommands=false
OverrideConsole=false
OverrideGameTime=false
OverrideJavaArgs=false
OverrideJavaLocation=false
OverrideMCLaunchMethod=false
OverrideMemory=false
OverrideNativeWorkarounds=false
OverrideWindow=false
iconKey=default
lastLaunchTime=%s
lastTimePlayed=0
name=%s
notes=
totalTimePlayed=0
""", Instant.now().toEpochMilli(), instanceDir.getFileName()));
}
Path overrides = fs.getPath(DOT_MINECRAFT);
Files.createDirectories(overrides);
if (meta.isFabric()) {
state.incrementStep("Adding mods");
if (!mds.isComplete()) throw new IOException("Mods dir scan is not yet completed");
modsLoop:
for (IWModDescription mod : mds.getMods()) {
if (!ModPath.isEnabled(mod.path())) continue;
state.updateStep(mod.path().toString());
if (ModPath.isImod(mod.path())) {
Set<ModSource> sources = mod.mod().orElseThrow().sources.keySet();
for (ModSource source : sources) {
Path jarPath = source.getJarPath();
Files.copy(jarPath, jarPath.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
}
Path md = overrides.resolve("mods");
Files.createDirectories(md);
Files.copy(mod.path(), md.resolve(mod.path().getFileName().toString()));
}
}
state.incrementStep("Adding files");
for (Path l : IgnoringWalk.walk(instanceDir).toList()) {
String fn = l.getFileName().toString();
if (fn.equals("mods") || fn.startsWith(".")) continue;
state.updateStep(fn);
Path target = overrides.resolve(fn);
JFiles.copyContent(l, target);
}
}
}
}

View File

@ -0,0 +1,100 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.model.multimc.MMCPackMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Set;
public class MultiMCExporter extends Exporter<MMCPackMeta> {
public MultiMCExporter() {
super("MultiMC", "zip", ".minecraft");
}
@Override
protected MMCPackMeta generateManifests(Path root, InstanceMeta instance, String name, String version) throws IOException {
{
Files.writeString(root.resolve("instance.cfg"), String.format("""
ForgeVersion=
InstanceType=OneSix
IntendedVersion=
JoinServerOnLaunch=false
LWJGLVersion=
LiteloaderVersion=
LogPrePostOutput=true
MCLaunchMethod=LauncherPart
OverrideCommands=false
OverrideConsole=false
OverrideGameTime=false
OverrideJavaArgs=false
OverrideJavaLocation=false
OverrideMCLaunchMethod=false
OverrideMemory=false
OverrideNativeWorkarounds=false
OverrideWindow=false
iconKey=default
lastLaunchTime=%s
lastTimePlayed=0
name=%s
notes=
totalTimePlayed=0
""", Instant.now().toEpochMilli(), name));
}
{
MMCPackMeta manifest = new MMCPackMeta();
manifest.formatVersion = 1;
manifest.components = new ArrayList<>();
MMCPackMeta.Component lwjgl = new MMCPackMeta.Component();
lwjgl.dependencyOnly = true;
lwjgl.uid = "org.lwjgl3";
lwjgl.version = "3.2.2"; //TODO get automatically
manifest.components.add(lwjgl);
MMCPackMeta.Component minecraft = new MMCPackMeta.Component();
minecraft.important = true;
minecraft.uid = "net.minecraft";
minecraft.version = instance.getMinecraftVersion();
manifest.components.add(minecraft);
if (instance.isFabric()) {
MMCPackMeta.Component intermediary = new MMCPackMeta.Component();
intermediary.dependencyOnly = true;
intermediary.uid = "net.fabricmc.intermediary";
intermediary.version = instance.getMinecraftVersion();
manifest.components.add(intermediary);
MMCPackMeta.Component fabric = new MMCPackMeta.Component();
fabric.uid = "net.fabricmc.fabric-loader";
fabric.version = instance.getLoaderVersion();
manifest.components.add(fabric);
}
JFiles.writeObject(root.resolve("mmc-pack.json"), manifest);
return manifest;
}
}
@Override
protected void addMods(Path root, InstanceMeta instance, Iterable<IWModDescription> mods, MMCPackMeta mmcPackMeta, Path modsOverrides) throws IOException {
modsLoop: for (IWModDescription mod : mods) {
if (ModPath.isImod(mod.path())) {
Set<ModSource> sources = mod.mod().orElseThrow().sources.keySet();
for (ModSource source : sources) {
Files.createDirectories(modsOverrides);
Files.copy(source.getJarPath(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
} else {
Files.createDirectories(modsOverrides);
Files.copy(mod.path(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
throw new FileNotFoundException("Could not find mod file for " + mod.path());
}
}
}

View File

@ -43,6 +43,6 @@ public class DownloadJavaStep implements Step {
}
}
//TODO link these in using a pre-launch class added to the launch classpath instead
JFiles.copyContent(MetaHolder.FORCE_LOAD_PATH, jvmDir.resolve("bin"));
JFiles.copyRecursive(MetaHolder.FORCE_LOAD_PATH, jvmDir.resolve("bin"));
}
}

View File

@ -41,7 +41,7 @@ public class DownloadLibrariesStep implements Step {
if (artifact.isNative) {
currentState.updateStep("Extracting natives");
try (FileSystem libFs = Utils.openZipFile(path, false)) {
JFiles.copyContent(libFs.getPath("."), MetaHolder.NATIVES_DIR.resolve(InstanceMeta.getMinecraftVersion(version.id)));
JFiles.copyRecursive(libFs.getPath("."), MetaHolder.NATIVES_DIR.resolve(InstanceMeta.getMinecraftVersion(version.id)));
} catch (Throwable t) {
Files.delete(path);
throw new IOException("Could not extract native", t);

View File

@ -6,8 +6,7 @@ import io.gitlab.jfronny.commons.tuple.Triple;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.common.Net;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeFile;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeMod;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.*;
import io.gitlab.jfronny.inceptum.launcher.api.CurseforgeApi;
import java.io.IOException;
@ -115,4 +114,12 @@ public final class CurseforgeModSource implements ModSource {
public int getProjectId() {
return projectId;
}
public CurseforgeModpackManifest.File toManifest() {
CurseforgeModpackManifest.File f = new CurseforgeModpackManifest.File();
f.projectID = getProjectId();
f.fileID = getFileId();
f.required = true;
return f;
}
}

View File

@ -0,0 +1,32 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import java.util.Objects;
import java.util.function.Supplier;
final class OnceSupplier<T> implements Supplier<T> {
private final T value;
private boolean supplied = false;
public OnceSupplier(T value) {
this.value = value;
}
@Override
public T get() {
if (supplied) throw new IllegalStateException("Attempted to use already used OnceSupplier");
supplied = true;
return value;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof OnceSupplier<?> that)) return false;
return Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
public record StreamIterable<T>(Supplier<Stream<T>> source) implements Iterable<T> {
public StreamIterable(Stream<T> source) {
this(new OnceSupplier<>(source));
}
@NotNull
@Override
public Iterator<T> iterator() {
return source.get().iterator();
}
@Override
public void forEach(Consumer<? super T> action) {
this.source.get().forEach(action);
}
}

View File

@ -55,7 +55,11 @@ public class IgnoreRule {
}
negate = isNegate;
for (Replacer replacer : REPLACERS) {
pattern = replacer.invoke(pattern);
try {
pattern = replacer.invoke(pattern);
} catch (Throwable t) {
throw new RuntimeException("Could not execute replacer " + replacer.getName() + " (" + replacer.getRegex() + ") on " + pattern, t);
}
}
parsedRegex = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).asMatchPredicate();

View File

@ -2,8 +2,7 @@ package io.gitlab.jfronny.inceptum.launcher.util.ignore;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@ -24,7 +23,7 @@ public class IgnoringWalk implements Iterator<Path> {
private IgnoringWalk(Path repositoryRoot) throws IOException {
this.ref = repositoryRoot;
enqueueContent(repositoryRoot);
enqueueDirectory(repositoryRoot);
}
@Override
@ -42,7 +41,7 @@ public class IgnoringWalk implements Iterator<Path> {
if (next == null) return false;
if (Files.isDirectory(next)) {
try {
enqueueContent(next);
enqueueDirectory(next);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
@ -50,7 +49,7 @@ public class IgnoringWalk implements Iterator<Path> {
return true;
}
private void enqueueContent(Path directory) throws IOException {
private void enqueueDirectory(Path directory) throws IOException {
Path gitignorePath = directory.resolve(GITIGNORE);
Path iceignorePath = directory.resolve(ICEIGNORE);
Ignore ignore = null;
@ -63,8 +62,8 @@ public class IgnoringWalk implements Iterator<Path> {
ignore.add(Files.readAllLines(iceignorePath));
}
if (ignore != null) ignores.put(directory, ignore);
try (Stream<Path> files = Files.list(directory)) {
for (Path path : files.toList()) {
try (DirectoryStream<Path> files = Files.newDirectoryStream(directory)) {
for (Path path : files) {
String fileName = path.getFileName().toString();
if (!fileName.equals(GITIGNORE)
&& !fileName.equals(ICEIGNORE)

View File

@ -1,8 +1,7 @@
package io.gitlab.jfronny.inceptum.launcher.util.ignore;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.regex.*;
public class Replacer {
private final String name;
@ -16,11 +15,19 @@ public class Replacer {
}
public String invoke(String pattern) {
return regex.matcher(pattern).replaceAll(matchEvaluator);
return regex.matcher(pattern).replaceAll(result -> Matcher.quoteReplacement(matchEvaluator.apply(result)));
}
@Override
public String toString() {
return name;
}
public String getName() {
return name;
}
public String getRegex() {
return regex.toString();
}
}