From fb3ba220b6c3284724c470ef5fc49333d753326d Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Wed, 9 Jan 2019 13:18:04 +0800 Subject: [PATCH] Fix redownloading disabled mods for curse modpack --- .../org/jackhuang/hmcl/setting/Profile.java | 7 - .../hmcl/ui/versions/ModListPage.java | 28 ++-- .../hmcl/game/DefaultGameRepository.java | 5 + .../hmcl/mod/CurseCompletionTask.java | 15 +- .../jackhuang/hmcl/mod/ForgeModMetadata.java | 4 +- .../jackhuang/hmcl/mod/LiteModMetadata.java | 5 +- .../java/org/jackhuang/hmcl/mod/ModInfo.java | 83 +++------- .../org/jackhuang/hmcl/mod/ModManager.java | 144 ++++++++++++++---- .../jackhuang/hmcl/mod/RiftModMetadata.java | 4 +- .../org/jackhuang/hmcl/util/StringUtils.java | 7 + 10 files changed, 177 insertions(+), 125 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java index f8d7dc009..961ceed7d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java @@ -31,7 +31,6 @@ import org.jackhuang.hmcl.event.RefreshedVersionsEvent; import org.jackhuang.hmcl.game.HMCLCacheRepository; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.Version; -import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty; @@ -52,7 +51,6 @@ import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; public final class Profile implements Observable { private final WeakListenerHolder listenerHolder = new WeakListenerHolder(); private final HMCLGameRepository repository; - private final ModManager modManager; private final StringProperty selectedVersion = new SimpleStringProperty(); @@ -136,7 +134,6 @@ public final class Profile implements Observable { this.name = new ImmediateStringProperty(this, "name", name); gameDir = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir); repository = new HMCLGameRepository(this, initialGameDir); - modManager = new ModManager(repository); this.global.set(global == null ? new VersionSetting() : global); this.selectedVersion.set(selectedVersion); this.useRelativePath.set(useRelativePath); @@ -166,10 +163,6 @@ public final class Profile implements Observable { return repository; } - public ModManager getModManager() { - return modManager; - } - public DefaultDependencyManager getDependency() { return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index ef4348e25..f6c20f8c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -44,7 +44,6 @@ public final class ModListPage extends ListPage { private JFXTabPane parentTab; private ModManager modManager; - private String versionId; public ModListPage() { setRefreshable(true); @@ -52,41 +51,44 @@ public final class ModListPage extends ListPage { FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> { mods.forEach(it -> { try { - modManager.addMod(versionId, it); + modManager.addMod(it); } catch (IOException | IllegalArgumentException e) { Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e); } }); - loadMods(modManager, versionId); + loadMods(modManager); }); } @Override public void refresh() { - loadMods(modManager, versionId); + loadMods(modManager); } public void loadVersion(Profile profile, String id) { - loadMods(profile.getModManager(), id); + loadMods(profile.getRepository().getModManager(id)); } - public void loadMods(ModManager modManager, String versionId) { + public void loadMods(ModManager modManager) { this.modManager = modManager; - this.versionId = versionId; Task.of(variables -> { synchronized (ModListPage.this) { JFXUtilities.runInFX(() -> loadingProperty().set(true)); - modManager.refreshMods(versionId); + modManager.refreshMods(); // Surprisingly, if there are a great number of mods, this processing will cause a long UI pause, // constructing UI elements. // We must do this asynchronously. LinkedList list = new LinkedList<>(); - for (ModInfo modInfo : modManager.getMods(versionId)) { + for (ModInfo modInfo : modManager.getMods()) { ModItem item = new ModItem(modInfo, i -> { - modManager.removeMods(versionId, modInfo); - loadMods(modManager, versionId); + try { + modManager.removeMods(modInfo); + } catch (IOException ignore) { + // Fail to remove mods if the game is running or the mod is absent. + } + loadMods(modManager); }); modInfo.activeProperty().addListener((a, b, newValue) -> { if (newValue) @@ -126,7 +128,7 @@ public final class ModListPage extends ListPage { Task.of(variables -> { for (File file : res) { try { - modManager.addMod(versionId, file); + modManager.addMod(file); succeeded.add(file.getName()); } catch (Exception e) { Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e); @@ -142,7 +144,7 @@ public final class ModListPage extends ListPage { if (!failed.isEmpty()) prompt.add(i18n("mods.add.failed", String.join(", ", failed))); Controllers.dialog(String.join("\n", prompt), i18n("mods.add")); - loadMods(modManager, versionId); + loadMods(modManager); })).start(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 73134754d..b963f2fbd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; import org.jackhuang.hmcl.event.*; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ToStringBuilder; @@ -366,6 +367,10 @@ public class DefaultGameRepository implements GameRepository { return getModpackConfiguration(version).exists(); } + public ModManager getModManager(String version) { + return new ModManager(this, version); + } + @Override public String toString() { return new ToStringBuilder(this) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java index 7c3db69c3..9bac795b1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.mod; import com.google.gson.JsonParseException; import org.jackhuang.hmcl.download.DefaultDependencyManager; -import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.*; @@ -45,8 +45,8 @@ import java.util.stream.Collectors; */ public final class CurseCompletionTask extends Task { - private final DefaultDependencyManager dependencyManager; - private final GameRepository repository; + private final DefaultGameRepository repository; + private final ModManager modManager; private final String version; private CurseManifest manifest = null; private final List dependents = new LinkedList<>(); @@ -70,8 +70,8 @@ public final class CurseCompletionTask extends Task { * @param manifest the CurseForgeModpack manifest. */ public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) { - this.dependencyManager = dependencyManager; this.repository = dependencyManager.getGameRepository(); + this.modManager = repository.getModManager(version); this.version = version; this.manifest = manifest; @@ -101,7 +101,6 @@ public final class CurseCompletionTask extends Task { return; File root = repository.getVersionRoot(version); - File run = repository.getRunDirectory(version); AtomicBoolean flag = new AtomicBoolean(true); AtomicInteger finished = new AtomicInteger(0); @@ -140,9 +139,9 @@ public final class CurseCompletionTask extends Task { for (CurseManifestFile file : newManifest.getFiles()) if (StringUtils.isNotBlank(file.getFileName())) { - File dest = new File(run, "mods/" + file.getFileName()); - if (!dest.exists()) - dependencies.add(new FileDownloadTask(file.getUrl(), dest)); + if (!modManager.hasSimpleMod(file.getFileName())) { + dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile())); + } } // Let this task fail if the curse manifest has not been completed. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java index 2aed863e3..7880b47ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java @@ -114,7 +114,7 @@ public final class ForgeModMetadata { return authors; } - public static ModInfo fromFile(File modFile) throws IOException, JsonParseException { + public static ModInfo fromFile(ModManager modManager, File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) @@ -132,7 +132,7 @@ public final class ForgeModMetadata { authors = String.join(", ", metadata.getAuthorList()); if (StringUtils.isBlank(authors)) authors = metadata.getCredits(); - return new ModInfo(modFile, metadata.getName(), metadata.getDescription(), + return new ModInfo(modManager, modFile, metadata.getName(), metadata.getDescription(), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java index 757100a6d..eedc64111 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java @@ -108,7 +108,7 @@ public final class LiteModMetadata { return updateURI; } - public static ModInfo fromFile(File modFile) throws IOException, JsonParseException { + public static ModInfo fromFile(ModManager modManager, File modFile) throws IOException, JsonParseException { try (ZipFile zipFile = new ZipFile(modFile)) { ZipEntry entry = zipFile.getEntry("litemod.json"); if (entry == null) @@ -116,7 +116,8 @@ public final class LiteModMetadata { LiteModMetadata metadata = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class); if (metadata == null) throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); - return new ModInfo(modFile, metadata.getName(), metadata.getDescription(), metadata.getAuthor(), metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI()); + return new ModInfo(modManager, modFile, metadata.getName(), metadata.getDescription(), metadata.getAuthor(), + metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI()); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java index c901b260b..290c7f6b3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java @@ -23,7 +23,10 @@ import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty; import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.Objects; +import java.util.logging.Level; /** * @@ -31,7 +34,7 @@ import java.util.Objects; */ public final class ModInfo implements Comparable { - private File file; + private Path file; private final String name; private final String description; private final String authors; @@ -41,16 +44,12 @@ public final class ModInfo implements Comparable { private final String fileName; private final ImmediateBooleanProperty activeProperty; - public ModInfo(File file, String name) { - this(file, name, ""); + public ModInfo(ModManager modManager, File file, String name, String description) { + this(modManager, file, name, description, "", "", "", ""); } - public ModInfo(File file, String name, String description) { - this(file, name, description, "", "", "", ""); - } - - public ModInfo(File file, String name, String description, String authors, String version, String gameVersion, String url) { - this.file = file; + public ModInfo(ModManager modManager, File file, String name, String description, String authors, String version, String gameVersion, String url) { + this.file = file.toPath(); this.name = name; this.description = description; this.authors = authors; @@ -58,26 +57,26 @@ public final class ModInfo implements Comparable { this.gameVersion = gameVersion; this.url = url; - activeProperty = new ImmediateBooleanProperty(this, "active", !DISABLED_EXTENSION.equals(FileUtils.getExtension(file))) { + activeProperty = new ImmediateBooleanProperty(this, "active", !modManager.isDisabled(file)) { @Override protected void invalidated() { - File f = ModInfo.this.file.getAbsoluteFile(), newF; - if (DISABLED_EXTENSION.equals(FileUtils.getExtension(f))) - newF = new File(f.getParentFile(), FileUtils.getNameWithoutExtension(f)); - else - newF = new File(f.getParentFile(), f.getName() + "." + DISABLED_EXTENSION); + Path path = ModInfo.this.file.toAbsolutePath(); - if (f.renameTo(newF)) - ModInfo.this.file = newF; - else - Logging.LOG.severe("Unable to rename file " + f + " to " + newF); + try { + if (get()) + ModInfo.this.file = modManager.enableMod(path); + else + ModInfo.this.file = modManager.disableMod(path); + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Unable to invert state of mod file " + path, e); + } } }; fileName = StringUtils.substringBeforeLast(isActive() ? file.getName() : FileUtils.getNameWithoutExtension(file), '.'); } - public File getFile() { + public Path getFile() { return file; } @@ -135,48 +134,4 @@ public final class ModInfo implements Comparable { public int hashCode() { return Objects.hash(getFileName()); } - - public static boolean isFileMod(File file) { - String name = file.getName(); - if (isDisabled(file)) - name = FileUtils.getNameWithoutExtension(file); - return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".litemod"); - } - - public static ModInfo fromFile(File modFile) { - File file = isDisabled(modFile) ? new File(modFile.getAbsoluteFile().getParentFile(), FileUtils.getNameWithoutExtension(modFile)) : modFile; - String description, extension = FileUtils.getExtension(file); - switch (extension) { - case "zip": - case "jar": - try { - return ForgeModMetadata.fromFile(modFile); - } catch (Exception ignore) { - } - - try { - return RiftModMetadata.fromFile(modFile); - } catch (Exception ignore) { - } - - description = ""; - break; - case "litemod": - try { - return LiteModMetadata.fromFile(modFile); - } catch (Exception ignore) { - description = "LiteLoader Mod"; - } - break; - default: - throw new IllegalArgumentException("File " + modFile + " is not a mod file."); - } - return new ModInfo(modFile, FileUtils.getNameWithoutExtension(modFile), description); - } - - public static boolean isDisabled(File file) { - return DISABLED_EXTENSION.equals(FileUtils.getExtension(file)); - } - - public static final String DISABLED_EXTENSION = "disabled"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 767a798de..71e8991da 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -18,50 +18,97 @@ package org.jackhuang.hmcl.mod; import org.jackhuang.hmcl.game.GameRepository; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.SimpleMultimap; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.*; -import java.util.function.Consumer; public final class ModManager { private final GameRepository repository; - private final SimpleMultimap modCache = new SimpleMultimap(HashMap::new, TreeSet::new); + private final String id; + private final TreeSet modInfos = new TreeSet<>(); - public ModManager(GameRepository repository) { + private boolean loaded = false; + + public ModManager(GameRepository repository, String id) { this.repository = repository; + this.id = id; } - public void refreshMods(String id) { - modCache.removeKey(id); - File modsDirectory = new File(repository.getRunDirectory(id), "mods"); - Consumer puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile))); - Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> { - if (modFile.isDirectory() && VersionNumber.isIntVersionNumber(modFile.getName())) { + private Path getModsDirectory() { + return repository.getRunDirectory(id).toPath().resolve("mods"); + } + + private void addModInfo(File file) { + try { + modInfos.add(getModInfo(file)); + } catch (IllegalArgumentException ignore) { + } + } + + public ModInfo getModInfo(File modFile) { + File file = isDisabled(modFile) ? new File(modFile.getAbsoluteFile().getParentFile(), FileUtils.getNameWithoutExtension(modFile)) : modFile; + String description, extension = FileUtils.getExtension(file); + switch (extension) { + case "zip": + case "jar": + try { + return ForgeModMetadata.fromFile(this, modFile); + } catch (Exception ignore) { + } + + try { + return RiftModMetadata.fromFile(this, modFile); + } catch (Exception ignore) { + } + + description = ""; + break; + case "litemod": + try { + return LiteModMetadata.fromFile(this, modFile); + } catch (Exception ignore) { + description = "LiteLoader Mod"; + } + break; + default: + throw new IllegalArgumentException("File " + modFile + " is not a mod file."); + } + return new ModInfo(this, modFile, FileUtils.getNameWithoutExtension(modFile), description); + } + + public void refreshMods() throws IOException { + modInfos.clear(); + for (Path subitem : Files.newDirectoryStream(getModsDirectory())) { + if (Files.isDirectory(subitem) && VersionNumber.isIntVersionNumber(FileUtils.getName(subitem))) { // If the folder name is game version, forge will search mod in this subdirectory - Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter)); + for (Path subsubitem : Files.newDirectoryStream(subitem)) + addModInfo(subsubitem.toFile()); } else { - puter.accept(modFile); + addModInfo(subitem.toFile()); } - })); + } + loaded = true; } - public Collection getMods(String id) { - if (!modCache.containsKey(id)) - refreshMods(id); - return modCache.get(id); + public Collection getMods() throws IOException { + if (!loaded) + refreshMods(); + return modInfos; } - public void addMod(String id, File file) throws IOException { - if (!ModInfo.isFileMod(file)) + public void addMod(File file) throws IOException { + if (!isFileMod(file)) throw new IllegalArgumentException("File " + file + " is not a valid mod file."); - if (!modCache.containsKey(id)) - refreshMods(id); + if (!loaded) + refreshMods(); File modsDirectory = new File(repository.getRunDirectory(id), "mods"); if (!FileUtils.makeDirectory(modsDirectory)) @@ -70,12 +117,55 @@ public final class ModManager { File newFile = new File(modsDirectory, file.getName()); FileUtils.copyFile(file, newFile); - modCache.put(id, ModInfo.fromFile(newFile)); + addModInfo(newFile); } - public boolean removeMods(String id, ModInfo... modInfos) { - boolean result = Arrays.stream(modInfos).reduce(true, (acc, modInfo) -> acc && modInfo.getFile().delete(), Boolean::logicalAnd); - refreshMods(id); - return result; + public void removeMods(ModInfo... modInfos) throws IOException { + for (ModInfo modInfo : modInfos) { + Files.deleteIfExists(modInfo.getFile()); + } + refreshMods(); } + + public Path disableMod(Path file) throws IOException { + Path disabled = file.getParent().resolve(StringUtils.addSuffix(FileUtils.getName(file), DISABLED_EXTENSION)); + if (Files.exists(file)) + Files.move(file, disabled, StandardCopyOption.REPLACE_EXISTING); + return disabled; + } + + public Path enableMod(Path file) throws IOException { + Path enabled = file.getParent().resolve(StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION)); + if (Files.exists(file)) + Files.move(file, enabled, StandardCopyOption.REPLACE_EXISTING); + return enabled; + } + + public boolean isDisabled(File file) { + return file.getPath().endsWith(DISABLED_EXTENSION); + } + + public boolean isFileMod(File file) { + String name = file.getName(); + if (isDisabled(file)) + name = FileUtils.getNameWithoutExtension(file); + return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".litemod"); + } + + /** + * Check if "mods" directory has mod file named "fileName" no matter the mod is disabled or not + * + * @param fileName name of the file whose existence is being checked + * @return true if the file exists + */ + public boolean hasSimpleMod(String fileName) { + return Files.exists(getModsDirectory().resolve(StringUtils.removeSuffix(fileName, DISABLED_EXTENSION))) + || Files.exists(getModsDirectory().resolve(StringUtils.addSuffix(fileName, DISABLED_EXTENSION))); + } + + public Path getSimpleModPath(String fileName) { + return getModsDirectory().resolve(fileName); + } + + public static final String DISABLED_EXTENSION = ".disabled"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RiftModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RiftModMetadata.java index 4063035c5..0a27db2cf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RiftModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RiftModMetadata.java @@ -60,14 +60,14 @@ public final class RiftModMetadata { return authors; } - public static ModInfo fromFile(File modFile) throws IOException, JsonParseException { + public static ModInfo fromFile(ModManager modManager, File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path mcmod = fs.getPath("riftmod.json"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Forge mod."); RiftModMetadata metadata = JsonUtils.fromNonNullJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)), RiftModMetadata.class); String authors = metadata.getAuthors() == null ? "" : String.join(", ", metadata.getAuthors()); - return new ModInfo(modFile, metadata.getName(), "", + return new ModInfo(modManager, modFile, metadata.getName(), "", authors, "", "", ""); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index 37461d183..aaf748381 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -146,6 +146,13 @@ public final class StringUtils { return prefix + str; } + public static String addSuffix(String str, String suffix) { + if (str.endsWith(suffix)) + return str; + else + return str + suffix; + } + public static String removePrefix(String str, String... prefixes) { for (String prefix : prefixes) if (str.startsWith(prefix))