Implement importing (untested)

This commit is contained in:
Johannes Frohnmeyer 2022-09-19 21:26:23 +02:00
parent 3480e0d965
commit 92109d353e
Signed by: Johannes
GPG Key ID: E76429612C2929F4
17 changed files with 400 additions and 37 deletions

View File

@ -13,6 +13,7 @@ public class CliMain {
new LaunchCommand(),
new ListCommand(),
new ModCommand(),
new ImportCommand(),
new ExportCommand(),
new JvmStateCommand(),
new BatchCommand()

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.log.OutputColors;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.nio.file.Paths;
import java.util.List;
public class ImportCommand extends Command {
public ImportCommand() {
super("Import a CurseForge, Modrinth or MultiMC instance", "<pack file>", List.of("import"), List.of());
}
@Override
protected void invoke(CommandArgs args) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a pack file");
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
String name = Importers.importPack(Paths.get(args.get(0)), state).getFileName().toString();
System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET);
}
}

View File

@ -15,18 +15,21 @@ import io.gitlab.jfronny.inceptum.imgui.window.Window;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.IntBuffer;
import java.nio.file.Files;
import java.nio.file.*;
import java.util.*;
public class GuiMain {
@ -246,4 +249,33 @@ public class GuiMain {
if (InceptumConfig.darkTheme) ImGui.styleColorsDark();
else ImGui.styleColorsLight();
}
public static @Nullable Path saveFileDialog(String title, String defaultName, String[] filters, String filterDescription) {
try (MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer filterPatterns = stack.mallocPointer(filters.length);
for (String filter : filters) {
filterPatterns.put(stack.UTF8(filter));
}
filterPatterns.flip();
String file = TinyFileDialogs.tinyfd_saveFileDialog(title, defaultName, filterPatterns, filterDescription);
return file == null ? null : Paths.get(file).toAbsolutePath().normalize();
}
}
public static List<Path> openFileDialog(String title, String defaultName, String[] filters, String filterDescription, boolean selectMultiple) {
try (MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer filterPatterns = stack.mallocPointer(filters.length);
for (String filter : filters) {
filterPatterns.put(stack.UTF8(filter));
}
filterPatterns.flip();
String files = TinyFileDialogs.tinyfd_openFileDialog(title, defaultName, filterPatterns, filterDescription, selectMultiple);
if (files == null) return List.of();
return Arrays.stream(files.split("\\|"))
.map(Paths::get)
.map(Path::toAbsolutePath)
.map(Path::normalize)
.toList();
}
}
}

View File

@ -12,6 +12,7 @@ import io.gitlab.jfronny.inceptum.launcher.model.inceptum.LoaderInfo;
import io.gitlab.jfronny.inceptum.launcher.model.mojang.*;
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi;
import io.gitlab.jfronny.inceptum.launcher.api.McApi;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceNameTool;
import java.io.IOException;
import java.nio.file.Files;
@ -42,7 +43,7 @@ public class InstanceManageControls {
}
}
version.set(getVersions(snapshots.get()).indexOf(selected));
name.set(defaultName == null ? getDefaultName(selected, fabric.get()) : defaultName);
name.set(defaultName == null ? InstanceNameTool.getDefaultName(selected.id, fabric.get()) : defaultName);
fabric.set(meta == null || meta.isFabric());
List<FabricVersionLoaderInfo> versions = getFabricLoaderInfo();
for (int i = 0, fabricLoaderInfoSize = versions.size(); i < fabricLoaderInfoSize; i++) {
@ -91,20 +92,14 @@ public class InstanceManageControls {
if (ImGui.combo("Version", version, vil.stream().map(info -> info.id).toArray(String[]::new))) {
VersionsListInfo prev = selected;
selected = vil.get(version.get());
if (getDefaultName(prev, fabric.get()).equals(name.get())) {
name.set(getDefaultName(selected, fabric.get()));
}
exchangeNameIfDefault(prev.id, fabric.get());
}
if (getFabricLoaderInfo().isEmpty()) {
if (fabric.get() && getDefaultName(selected, true).equals(name.get())) {
name.set(getDefaultName(selected, false));
}
fabric.set(false);
exchangeNameIfDefault(selected.id, true);
} else {
if (ImGui.checkbox("Fabric support", fabric)) {
if (getDefaultName(selected, !fabric.get()).equals(name.get())) {
name.set(getDefaultName(selected, fabric.get()));
}
exchangeNameIfDefault(selected.id, !fabric.get());
}
if (fabric.get()) {
ImGui.sameLine();
@ -122,6 +117,18 @@ public class InstanceManageControls {
}
}
private void exchangeNameIfDefault(String id, boolean fabric) {
if (InstanceNameTool.getDefaultName(id, fabric).equals(InstanceNameTool.namePart(this.name.get()))) {
String defName = InstanceNameTool.getDefaultName(this.selected.id, this.fabric.get());
try {
defName = InstanceNameTool.getNextValid(defName);
} catch (IOException e) {
Utils.LOGGER.error("Could not find valid number suffix", e);
}
name.set(defName);
}
}
public VersionInfo getVersionInfo() throws IOException {
return VERSION_INFO_CACHE.get(Tuple.of(selected.id, fabric.get() ? selectedFabric.loader.version : ""), () -> {
VersionInfo vi = McApi.getVersionInfo(selected);
@ -141,10 +148,6 @@ public class InstanceManageControls {
return res;
}
private String getDefaultName(VersionsListInfo info, boolean fabric) {
return fabric ? "Fabric " + info.id : info.id;
}
private List<FabricVersionLoaderInfo> getFabricLoaderInfo() {
return LOADER_INFO_CACHE.get(selected, () -> FabricMetaApi.getLoaderVersions(selected));
}

View File

@ -3,11 +3,18 @@ package io.gitlab.jfronny.inceptum.imgui.window;
import imgui.ImGui;
import io.gitlab.jfronny.inceptum.common.R;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.imgui.GuiMain;
import io.gitlab.jfronny.inceptum.imgui.control.InstanceManageControls;
import io.gitlab.jfronny.inceptum.imgui.window.dialog.ProcessStateWatcherWindow;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
import io.gitlab.jfronny.inceptum.launcher.system.install.SetupStepInfo;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public class NewInstanceWindow extends Window {
private final InstanceManageControls imc = new InstanceManageControls(null, null);
@ -39,8 +46,19 @@ public class NewInstanceWindow extends Window {
ImGui.text("You can also just add an instance directory and it'll be loaded automatically");
ImGui.text("Using git to manage it is recommended if you do so");
ImGui.spacing();
ImGui.text("Importing CurseForge or Modrinth packs is not yet implemented");
//TODO generic importer based on zip contents and file name ("zip", "mrpack")
ImGui.text("Below, you can import a CurseForge, Modrinth or MultiMC pack");
if (ImGui.button("Import")) {
List<Path> packs = GuiMain.openFileDialog("Import Pack", null, new String[] {"*.zip", "*.mrpack"}, "Modpack", true);
if (!packs.isEmpty()) {
ProcessState state = new ProcessState(Importers.MAX_STEPS * packs.size(), "Initializing");
GuiMain.open(new ProcessStateWatcherWindow("Importing", "Could not import packs", state, cToken -> {
for (Path pack : packs) {
Path imported = Importers.importPack(pack, state);
LauncherEnv.showInfo(pack.getFileName() + " has been successfully imported as " + imported.getFileName(), "Imported pack");
}
}, null));
}
}
ImGui.endTabItem();
}
ImGui.endTabBar();

View File

@ -10,9 +10,6 @@ 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;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -33,11 +30,10 @@ public class ExportTab extends Tab {
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) {
Path exportPath = GuiMain.saveFileDialog("Export " + exporter.getName() + " Pack", defaultName, new String[] {filter}, exporter.getName() + " packs (" + filter + ")");
if (exportPath != 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));
@ -49,13 +45,4 @@ public class ExportTab extends Tab {
ImGui.text("The mods directory scan must be completed.\nThe progress for this can be observed in the mods tab");
}
}
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

@ -27,7 +27,10 @@ public class CleanupFileVisitor implements FileVisitor<Path> {
}
@Override
public FileVisitResult postVisitDirectory(Path path, IOException e) {
public FileVisitResult postVisitDirectory(Path path, IOException e) throws IOException {
if (Files.exists(path) && JFiles.list(path).isEmpty()) {
Files.delete(path);
}
return FileVisitResult.CONTINUE;
}
}

View File

@ -19,7 +19,7 @@ import java.util.Set;
public class CurseForgeExporter extends Exporter<CurseforgeModpackManifest> {
private static final String OVERRIDES_DIR_DEFAULT = "overrides";
public CurseForgeExporter() {
super("CurseForge", "zip", "overrides");
super("CurseForge", "zip", OVERRIDES_DIR_DEFAULT);
}
@Override

View File

@ -1,6 +1,5 @@
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;
@ -11,7 +10,6 @@ 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> {

View File

@ -0,0 +1,50 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeModpackManifest;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.ModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.source.*;
import io.gitlab.jfronny.inceptum.launcher.util.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
public class CurseForgeImporter extends Importer<CurseforgeModpackManifest> {
public CurseForgeImporter() {
super("CurseForge", "manifest.json", CurseforgeModpackManifest.class);
}
@Override
protected IntermediaryManifest validateManifest(CurseforgeModpackManifest manifest, Path sourceRoot) throws IOException {
if (manifest.manifestVersion != 1) throw new IOException("Unsupported CurseForge modpack manifest version");
if (!"minecraftModpack".equals(manifest.manifestType)) throw new IOException("Invalid input: not a minecraft modpack");
if (manifest.minecraft == null) throw new IOException("Modpack missing minecraft metadata");
if (manifest.minecraft.modLoaders == null) manifest.minecraft.modLoaders = Set.of();
if (manifest.minecraft.version == null) throw new IOException("Modpack missing minecraft version");
if (manifest.files == null) manifest.files = Set.of();
String fabric = null;
for (CurseforgeModpackManifest.Minecraft.ModLoader loader : manifest.minecraft.modLoaders) {
final String idPrefix = "fabric-";
if (!loader.id.startsWith(idPrefix)) throw new IOException("Unsupported mod loader");
fabric = loader.id.substring(idPrefix.length());
}
return new IntermediaryManifest(manifest.name, manifest.overrides, manifest.minecraft.version, fabric);
}
@Override
protected void downloadMods(CurseforgeModpackManifest manifest, Path instanceDir, ProcessState state) throws IOException {
Path modsPath = instanceDir.resolve("mods");
for (CurseforgeModpackManifest.File file : manifest.files) {
if (!file.required) continue;
CurseforgeModSource source = new CurseforgeModSource(file.projectID, file.fileID);
state.updateStep("Downloading " + source.getName());
ModDownload download = source.download();
ModDescription imod = ModDescription.of(download.sha1(), download.murmur2(), source, manifest.minecraft.version);
Files.createDirectories(modsPath);
JFiles.writeObject(modsPath.resolve(source.getShortName() + ModPath.EXT_IMOD), imod);
}
}
}

View File

@ -0,0 +1,78 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi;
import io.gitlab.jfronny.inceptum.launcher.api.McApi;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
import io.gitlab.jfronny.inceptum.launcher.util.*;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public abstract class Importer<T> {
private final String name;
private final String manifestFile;
private final Class<T> manifestClass;
public Importer(String name, String manifestFile, Class<T> manifestClass) {
this.name = name;
this.manifestFile = manifestFile;
this.manifestClass = manifestClass;
}
public boolean canImport(Path root) {
return Files.exists(root.resolve(manifestFile));
}
protected abstract IntermediaryManifest validateManifest(T manifest, Path sourceRoot) throws IOException;
protected abstract void downloadMods(T manifest, Path instanceDir, ProcessState state) throws IOException;
private String createVersionString(String gameVersion, @Nullable String fabricVersion) throws IOException {
if (McApi.getVersions().versions.stream().filter(v -> v.id.equals(gameVersion)).findFirst().isEmpty()) {
throw new IOException("Invalid minecraft version: " + gameVersion);
}
if (fabricVersion == null) {
return gameVersion;
} else {
FabricMetaApi.getLoaderVersion(gameVersion, fabricVersion);
return InstanceMeta.floaderPrefix + fabricVersion + '-' + gameVersion;
}
}
public Path importPack(Path sourceRoot, ProcessState state) throws IOException {
state.incrementStep("Generating skeleton");
T manifest = JFiles.readObject(sourceRoot.resolve(manifestFile), manifestClass);
IntermediaryManifest man = validateManifest(manifest, sourceRoot);
String name = man.name();
if (name == null || !Utils.VALID_FILENAME.matcher(name).matches()) name = "New Pack";
name = InstanceNameTool.getNextValid(name);
final Path iDir = MetaHolder.INSTANCE_DIR.resolve(name).toAbsolutePath().normalize();
InstanceLock.setSetupLock(iDir, true);
InstanceMeta meta = new InstanceMeta();
meta.version = createVersionString(man.gameVersion(), man.fabricVersion());
JFiles.writeObject(iDir.resolve("instance.json"), meta);
state.incrementStep("Downloading mods");
downloadMods(manifest, iDir, state);
state.incrementStep("Adding overrides");
if (man.overridesPath() != null) {
Path overridesPath = sourceRoot.resolve(man.overridesPath());
JFiles.listTo(overridesPath, path -> {
JFiles.copyRecursive(path, iDir.resolve(path.getFileName().toString()));
});
}
InstanceLock.setSetupLock(iDir, false);
Steps.reDownload(iDir, state);
return iDir;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,32 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.List;
public class Importers {
public static final int MAX_STEPS = Steps.STEPS.size() + 3;
public static final CurseForgeImporter CURSE_FORGE = new CurseForgeImporter();
public static final ModrinthImporter MODRINTH = new ModrinthImporter();
public static final MultiMCImporter MULTI_MC = new MultiMCImporter();
public static final List<Importer<?>> IMPORTERS = List.of(CURSE_FORGE, MODRINTH, MULTI_MC);
public static Path importPack(Path zipPath, ProcessState state) throws IOException {
try (FileSystem fs = Utils.openZipFile(zipPath, false)) {
for (Importer<?> importer : IMPORTERS) {
if (importer.canImport(fs.getPath("."))) {
return importer.importPack(fs.getPath("."), state);
}
}
} catch (URISyntaxException e) {
throw new IOException("Could not open zip", e);
}
throw new IOException("Could not import pack: unsupported format");
}
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import org.jetbrains.annotations.Nullable;
public record IntermediaryManifest(String name, @Nullable String overridesPath, String gameVersion, @Nullable String fabricVersion) {
}

View File

@ -0,0 +1,55 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import io.gitlab.jfronny.inceptum.common.Net;
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.ModrinthModpackManifest;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
public class ModrinthImporter extends Importer<ModrinthModpackManifest> {
public ModrinthImporter() {
super("Modrinth", "modrinth.index.json", ModrinthModpackManifest.class);
}
@Override
protected IntermediaryManifest validateManifest(ModrinthModpackManifest manifest, Path sourceRoot) throws IOException {
if (manifest.formatVersion != 1) throw new IOException("Unsupported Modrinth modpack manifest version");
if (!"minecraft".equals(manifest.game)) throw new IOException("Invalid input: not a minecraft modpack");
if (manifest.files == null) manifest.files = new ArrayList<>();
if (manifest.dependencies == null) manifest.dependencies = new ModrinthModpackManifest.Dependencies();
if (manifest.dependencies.minecraft == null) throw new IOException("Could not find minecraft version");
if (manifest.dependencies.fabricLoader == null) {
for (ModrinthModpackManifest.File file : manifest.files) {
if (file.path == null) {
throw new IOException("File lacks path");
}
if (file.path.startsWith("mods")) {
throw new IOException("Found mod files but no fabric loader. Other loaders are unsupported!");
}
}
}
return new IntermediaryManifest(manifest.name, "overrides", manifest.dependencies.minecraft, manifest.dependencies.fabricLoader);
}
@Override
protected void downloadMods(ModrinthModpackManifest manifest, Path instanceDir, ProcessState state) throws IOException {
filesLoop: for (ModrinthModpackManifest.File file : manifest.files) {
Path path = instanceDir.getParent().resolve(file.path).toAbsolutePath().normalize();
if (!path.startsWith(instanceDir)) throw new IOException("Pack attempted to write file outside instance. This is unsupported!");
String sha1 = file.hashes.sha1;
IOException failedDownload = new IOException("Could not download file");
for (String url : file.downloads) {
try {
Net.downloadFile(url, sha1, path);
continue filesLoop;
} catch (IOException | URISyntaxException e) {
failedDownload.addSuppressed(e);
}
}
throw failedDownload;
}
}
}

View File

@ -0,0 +1,45 @@
package io.gitlab.jfronny.inceptum.launcher.system.importer;
import io.gitlab.jfronny.inceptum.launcher.model.multimc.MMCPackMeta;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class MultiMCImporter extends Importer<MMCPackMeta> {
public MultiMCImporter() {
super("MultiMC", "mmc-pack.json", MMCPackMeta.class);
}
@Override
protected IntermediaryManifest validateManifest(MMCPackMeta manifest, Path sourceRoot) throws IOException {
if (manifest.formatVersion != 1) throw new IOException("Unsupported MultiMC format version");
if (manifest.components == null) throw new IOException("MultiMC pack missing components");
String gameVersion = null;
String fabricVersion = null;
String name = null;
Path instanceCfg = sourceRoot.resolve("instance.cfg");
if (Files.exists(instanceCfg)) {
try (Stream<String> lines = Files.lines(instanceCfg)) {
final String namePrefix = "name=";
name = lines.filter(l -> l.startsWith(namePrefix))
.map(l -> l.substring(namePrefix.length()))
.findFirst()
.orElse(null);
}
}
for (MMCPackMeta.Component component : manifest.components) {
if ("net.minecraft".equals(component.uid)) gameVersion = component.version;
if ("net.fabricmc.fabric-loader".equals(component.uid)) fabricVersion = component.version;
}
if (gameVersion == null) throw new IOException("Pack lacks minecraft component");
return new IntermediaryManifest(name, ".minecraft", gameVersion, fabricVersion);
}
@Override
protected void downloadMods(MMCPackMeta manifest, Path instanceDir, ProcessState state) throws IOException {
// All mods are overrides, so do nothing
}
}

View File

@ -46,7 +46,7 @@ public record FileScanTask(Path file, BiConsumer<Path, IWModDescription> discove
}
if (ModPath.isImod(file)) {
String fn = file.getFileName().toString();
Path modFile = file.getParent().resolve(fn.substring(0, fn.length() - 5));
Path modFile = file.getParent().resolve(fn.substring(0, fn.length() - ModPath.EXT_IMOD.length()));
final var imod = new Object() {
Path i = file;
ModDescription md = JFiles.readObject(file, ModDescription.class);

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import java.io.IOException;
import java.nio.file.Files;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class InstanceNameTool {
private static final Pattern NUMBER_PATTERN = Pattern.compile("^(.*)\\s*\\(\\d+\\)$");
public static String getDefaultName(String versionId, boolean fabric) {
return fabric ? "Fabric " + versionId : versionId;
}
public static String namePart(String name) {
Matcher matcher = NUMBER_PATTERN.matcher(name);
return matcher.matches() ? matcher.group(1) : name;
}
public static String getNextValid(String name) throws IOException {
name = namePart(name);
if (!Files.exists(MetaHolder.INSTANCE_DIR.resolve(name))) return name;
long l = 0;
String numberedName;
do numberedName = name + " (" + ++l + ")";
while (Files.exists(MetaHolder.INSTANCE_DIR.resolve(numberedName)));
return numberedName;
}
}