mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-11 04:46:18 -04:00
Fix redownloading disabled mods for curse modpack
This commit is contained in:
parent
d655c9ec6b
commit
fb3ba220b6
@ -31,7 +31,6 @@ import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
|||||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
import org.jackhuang.hmcl.game.Version;
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.mod.ModManager;
|
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
import org.jackhuang.hmcl.util.javafx.ImmediateObjectProperty;
|
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 {
|
public final class Profile implements Observable {
|
||||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
private final HMCLGameRepository repository;
|
private final HMCLGameRepository repository;
|
||||||
private final ModManager modManager;
|
|
||||||
|
|
||||||
private final StringProperty selectedVersion = new SimpleStringProperty();
|
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||||
|
|
||||||
@ -136,7 +134,6 @@ public final class Profile implements Observable {
|
|||||||
this.name = new ImmediateStringProperty(this, "name", name);
|
this.name = new ImmediateStringProperty(this, "name", name);
|
||||||
gameDir = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
|
gameDir = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
|
||||||
repository = new HMCLGameRepository(this, initialGameDir);
|
repository = new HMCLGameRepository(this, initialGameDir);
|
||||||
modManager = new ModManager(repository);
|
|
||||||
this.global.set(global == null ? new VersionSetting() : global);
|
this.global.set(global == null ? new VersionSetting() : global);
|
||||||
this.selectedVersion.set(selectedVersion);
|
this.selectedVersion.set(selectedVersion);
|
||||||
this.useRelativePath.set(useRelativePath);
|
this.useRelativePath.set(useRelativePath);
|
||||||
@ -166,10 +163,6 @@ public final class Profile implements Observable {
|
|||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModManager getModManager() {
|
|
||||||
return modManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultDependencyManager getDependency() {
|
public DefaultDependencyManager getDependency() {
|
||||||
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,6 @@ public final class ModListPage extends ListPage<ModItem> {
|
|||||||
|
|
||||||
private JFXTabPane parentTab;
|
private JFXTabPane parentTab;
|
||||||
private ModManager modManager;
|
private ModManager modManager;
|
||||||
private String versionId;
|
|
||||||
|
|
||||||
public ModListPage() {
|
public ModListPage() {
|
||||||
setRefreshable(true);
|
setRefreshable(true);
|
||||||
@ -52,41 +51,44 @@ public final class ModListPage extends ListPage<ModItem> {
|
|||||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||||
mods.forEach(it -> {
|
mods.forEach(it -> {
|
||||||
try {
|
try {
|
||||||
modManager.addMod(versionId, it);
|
modManager.addMod(it);
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
loadMods(modManager, versionId);
|
loadMods(modManager);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
loadMods(modManager, versionId);
|
loadMods(modManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadVersion(Profile profile, String id) {
|
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.modManager = modManager;
|
||||||
this.versionId = versionId;
|
|
||||||
Task.of(variables -> {
|
Task.of(variables -> {
|
||||||
synchronized (ModListPage.this) {
|
synchronized (ModListPage.this) {
|
||||||
JFXUtilities.runInFX(() -> loadingProperty().set(true));
|
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,
|
// Surprisingly, if there are a great number of mods, this processing will cause a long UI pause,
|
||||||
// constructing UI elements.
|
// constructing UI elements.
|
||||||
// We must do this asynchronously.
|
// We must do this asynchronously.
|
||||||
LinkedList<ModItem> list = new LinkedList<>();
|
LinkedList<ModItem> list = new LinkedList<>();
|
||||||
for (ModInfo modInfo : modManager.getMods(versionId)) {
|
for (ModInfo modInfo : modManager.getMods()) {
|
||||||
ModItem item = new ModItem(modInfo, i -> {
|
ModItem item = new ModItem(modInfo, i -> {
|
||||||
modManager.removeMods(versionId, modInfo);
|
try {
|
||||||
loadMods(modManager, versionId);
|
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) -> {
|
modInfo.activeProperty().addListener((a, b, newValue) -> {
|
||||||
if (newValue)
|
if (newValue)
|
||||||
@ -126,7 +128,7 @@ public final class ModListPage extends ListPage<ModItem> {
|
|||||||
Task.of(variables -> {
|
Task.of(variables -> {
|
||||||
for (File file : res) {
|
for (File file : res) {
|
||||||
try {
|
try {
|
||||||
modManager.addMod(versionId, file);
|
modManager.addMod(file);
|
||||||
succeeded.add(file.getName());
|
succeeded.add(file.getName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
|
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
|
||||||
@ -142,7 +144,7 @@ public final class ModListPage extends ListPage<ModItem> {
|
|||||||
if (!failed.isEmpty())
|
if (!failed.isEmpty())
|
||||||
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
|
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
|
||||||
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
|
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
|
||||||
loadMods(modManager, versionId);
|
loadMods(modManager);
|
||||||
})).start();
|
})).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package org.jackhuang.hmcl.game;
|
|||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import org.jackhuang.hmcl.event.*;
|
import org.jackhuang.hmcl.event.*;
|
||||||
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||||
@ -366,6 +367,10 @@ public class DefaultGameRepository implements GameRepository {
|
|||||||
return getModpackConfiguration(version).exists();
|
return getModpackConfiguration(version).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModManager getModManager(String version) {
|
||||||
|
return new ModManager(this, version);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this)
|
return new ToStringBuilder(this)
|
||||||
|
@ -19,7 +19,7 @@ package org.jackhuang.hmcl.mod;
|
|||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
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.FileDownloadTask;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
@ -45,8 +45,8 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public final class CurseCompletionTask extends Task {
|
public final class CurseCompletionTask extends Task {
|
||||||
|
|
||||||
private final DefaultDependencyManager dependencyManager;
|
private final DefaultGameRepository repository;
|
||||||
private final GameRepository repository;
|
private final ModManager modManager;
|
||||||
private final String version;
|
private final String version;
|
||||||
private CurseManifest manifest = null;
|
private CurseManifest manifest = null;
|
||||||
private final List<Task> dependents = new LinkedList<>();
|
private final List<Task> dependents = new LinkedList<>();
|
||||||
@ -70,8 +70,8 @@ public final class CurseCompletionTask extends Task {
|
|||||||
* @param manifest the CurseForgeModpack manifest.
|
* @param manifest the CurseForgeModpack manifest.
|
||||||
*/
|
*/
|
||||||
public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) {
|
public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) {
|
||||||
this.dependencyManager = dependencyManager;
|
|
||||||
this.repository = dependencyManager.getGameRepository();
|
this.repository = dependencyManager.getGameRepository();
|
||||||
|
this.modManager = repository.getModManager(version);
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
|
|
||||||
@ -101,7 +101,6 @@ public final class CurseCompletionTask extends Task {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
File root = repository.getVersionRoot(version);
|
File root = repository.getVersionRoot(version);
|
||||||
File run = repository.getRunDirectory(version);
|
|
||||||
|
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
AtomicBoolean flag = new AtomicBoolean(true);
|
||||||
AtomicInteger finished = new AtomicInteger(0);
|
AtomicInteger finished = new AtomicInteger(0);
|
||||||
@ -140,9 +139,9 @@ public final class CurseCompletionTask extends Task {
|
|||||||
|
|
||||||
for (CurseManifestFile file : newManifest.getFiles())
|
for (CurseManifestFile file : newManifest.getFiles())
|
||||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||||
File dest = new File(run, "mods/" + file.getFileName());
|
if (!modManager.hasSimpleMod(file.getFileName())) {
|
||||||
if (!dest.exists())
|
dependencies.add(new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile()));
|
||||||
dependencies.add(new FileDownloadTask(file.getUrl(), dest));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let this task fail if the curse manifest has not been completed.
|
// Let this task fail if the curse manifest has not been completed.
|
||||||
|
@ -114,7 +114,7 @@ public final class ForgeModMetadata {
|
|||||||
return authors;
|
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())) {
|
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) {
|
||||||
Path mcmod = fs.getPath("mcmod.info");
|
Path mcmod = fs.getPath("mcmod.info");
|
||||||
if (Files.notExists(mcmod))
|
if (Files.notExists(mcmod))
|
||||||
@ -132,7 +132,7 @@ public final class ForgeModMetadata {
|
|||||||
authors = String.join(", ", metadata.getAuthorList());
|
authors = String.join(", ", metadata.getAuthorList());
|
||||||
if (StringUtils.isBlank(authors))
|
if (StringUtils.isBlank(authors))
|
||||||
authors = metadata.getCredits();
|
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(),
|
authors, metadata.getVersion(), metadata.getGameVersion(),
|
||||||
StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url);
|
StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url);
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ public final class LiteModMetadata {
|
|||||||
return updateURI;
|
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)) {
|
try (ZipFile zipFile = new ZipFile(modFile)) {
|
||||||
ZipEntry entry = zipFile.getEntry("litemod.json");
|
ZipEntry entry = zipFile.getEntry("litemod.json");
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
@ -116,7 +116,8 @@ public final class LiteModMetadata {
|
|||||||
LiteModMetadata metadata = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class);
|
LiteModMetadata metadata = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class);
|
||||||
if (metadata == null)
|
if (metadata == null)
|
||||||
throw new IOException("Mod " + modFile + " `litemod.json` is malformed.");
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,10 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
|||||||
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
|
import org.jackhuang.hmcl.util.javafx.ImmediateBooleanProperty;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -31,7 +34,7 @@ import java.util.Objects;
|
|||||||
*/
|
*/
|
||||||
public final class ModInfo implements Comparable<ModInfo> {
|
public final class ModInfo implements Comparable<ModInfo> {
|
||||||
|
|
||||||
private File file;
|
private Path file;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String description;
|
private final String description;
|
||||||
private final String authors;
|
private final String authors;
|
||||||
@ -41,16 +44,12 @@ public final class ModInfo implements Comparable<ModInfo> {
|
|||||||
private final String fileName;
|
private final String fileName;
|
||||||
private final ImmediateBooleanProperty activeProperty;
|
private final ImmediateBooleanProperty activeProperty;
|
||||||
|
|
||||||
public ModInfo(File file, String name) {
|
public ModInfo(ModManager modManager, File file, String name, String description) {
|
||||||
this(file, name, "");
|
this(modManager, file, name, description, "", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModInfo(File file, String name, String description) {
|
public ModInfo(ModManager modManager, File file, String name, String description, String authors, String version, String gameVersion, String url) {
|
||||||
this(file, name, description, "", "", "", "");
|
this.file = file.toPath();
|
||||||
}
|
|
||||||
|
|
||||||
public ModInfo(File file, String name, String description, String authors, String version, String gameVersion, String url) {
|
|
||||||
this.file = file;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.authors = authors;
|
this.authors = authors;
|
||||||
@ -58,26 +57,26 @@ public final class ModInfo implements Comparable<ModInfo> {
|
|||||||
this.gameVersion = gameVersion;
|
this.gameVersion = gameVersion;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
||||||
activeProperty = new ImmediateBooleanProperty(this, "active", !DISABLED_EXTENSION.equals(FileUtils.getExtension(file))) {
|
activeProperty = new ImmediateBooleanProperty(this, "active", !modManager.isDisabled(file)) {
|
||||||
@Override
|
@Override
|
||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
File f = ModInfo.this.file.getAbsoluteFile(), newF;
|
Path path = ModInfo.this.file.toAbsolutePath();
|
||||||
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);
|
|
||||||
|
|
||||||
if (f.renameTo(newF))
|
try {
|
||||||
ModInfo.this.file = newF;
|
if (get())
|
||||||
|
ModInfo.this.file = modManager.enableMod(path);
|
||||||
else
|
else
|
||||||
Logging.LOG.severe("Unable to rename file " + f + " to " + newF);
|
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), '.');
|
fileName = StringUtils.substringBeforeLast(isActive() ? file.getName() : FileUtils.getNameWithoutExtension(file), '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public Path getFile() {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,48 +134,4 @@ public final class ModInfo implements Comparable<ModInfo> {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(getFileName());
|
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";
|
|
||||||
}
|
}
|
||||||
|
@ -18,50 +18,97 @@
|
|||||||
package org.jackhuang.hmcl.mod;
|
package org.jackhuang.hmcl.mod;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.game.GameRepository;
|
import org.jackhuang.hmcl.game.GameRepository;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
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.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public final class ModManager {
|
public final class ModManager {
|
||||||
private final GameRepository repository;
|
private final GameRepository repository;
|
||||||
private final SimpleMultimap<String, ModInfo> modCache = new SimpleMultimap<String, ModInfo>(HashMap::new, TreeSet::new);
|
private final String id;
|
||||||
|
private final TreeSet<ModInfo> modInfos = new TreeSet<>();
|
||||||
|
|
||||||
public ModManager(GameRepository repository) {
|
private boolean loaded = false;
|
||||||
|
|
||||||
|
public ModManager(GameRepository repository, String id) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshMods(String id) {
|
private Path getModsDirectory() {
|
||||||
modCache.removeKey(id);
|
return repository.getRunDirectory(id).toPath().resolve("mods");
|
||||||
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
}
|
||||||
Consumer<File> puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile)));
|
|
||||||
Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> {
|
private void addModInfo(File file) {
|
||||||
if (modFile.isDirectory() && VersionNumber.isIntVersionNumber(modFile.getName())) {
|
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
|
// 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 {
|
} else {
|
||||||
puter.accept(modFile);
|
addModInfo(subitem.toFile());
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ModInfo> getMods(String id) {
|
public Collection<ModInfo> getMods() throws IOException {
|
||||||
if (!modCache.containsKey(id))
|
if (!loaded)
|
||||||
refreshMods(id);
|
refreshMods();
|
||||||
return modCache.get(id);
|
return modInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMod(String id, File file) throws IOException {
|
public void addMod(File file) throws IOException {
|
||||||
if (!ModInfo.isFileMod(file))
|
if (!isFileMod(file))
|
||||||
throw new IllegalArgumentException("File " + file + " is not a valid mod file.");
|
throw new IllegalArgumentException("File " + file + " is not a valid mod file.");
|
||||||
|
|
||||||
if (!modCache.containsKey(id))
|
if (!loaded)
|
||||||
refreshMods(id);
|
refreshMods();
|
||||||
|
|
||||||
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
||||||
if (!FileUtils.makeDirectory(modsDirectory))
|
if (!FileUtils.makeDirectory(modsDirectory))
|
||||||
@ -70,12 +117,55 @@ public final class ModManager {
|
|||||||
File newFile = new File(modsDirectory, file.getName());
|
File newFile = new File(modsDirectory, file.getName());
|
||||||
FileUtils.copyFile(file, newFile);
|
FileUtils.copyFile(file, newFile);
|
||||||
|
|
||||||
modCache.put(id, ModInfo.fromFile(newFile));
|
addModInfo(newFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeMods(String id, ModInfo... modInfos) {
|
public void removeMods(ModInfo... modInfos) throws IOException {
|
||||||
boolean result = Arrays.stream(modInfos).reduce(true, (acc, modInfo) -> acc && modInfo.getFile().delete(), Boolean::logicalAnd);
|
for (ModInfo modInfo : modInfos) {
|
||||||
refreshMods(id);
|
Files.deleteIfExists(modInfo.getFile());
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
@ -60,14 +60,14 @@ public final class RiftModMetadata {
|
|||||||
return authors;
|
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())) {
|
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) {
|
||||||
Path mcmod = fs.getPath("riftmod.json");
|
Path mcmod = fs.getPath("riftmod.json");
|
||||||
if (Files.notExists(mcmod))
|
if (Files.notExists(mcmod))
|
||||||
throw new IOException("File " + modFile + " is not a Forge mod.");
|
throw new IOException("File " + modFile + " is not a Forge mod.");
|
||||||
RiftModMetadata metadata = JsonUtils.fromNonNullJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)), RiftModMetadata.class);
|
RiftModMetadata metadata = JsonUtils.fromNonNullJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)), RiftModMetadata.class);
|
||||||
String authors = metadata.getAuthors() == null ? "" : String.join(", ", metadata.getAuthors());
|
String authors = metadata.getAuthors() == null ? "" : String.join(", ", metadata.getAuthors());
|
||||||
return new ModInfo(modFile, metadata.getName(), "",
|
return new ModInfo(modManager, modFile, metadata.getName(), "",
|
||||||
authors, "", "", "");
|
authors, "", "", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +146,13 @@ public final class StringUtils {
|
|||||||
return prefix + str;
|
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) {
|
public static String removePrefix(String str, String... prefixes) {
|
||||||
for (String prefix : prefixes)
|
for (String prefix : prefixes)
|
||||||
if (str.startsWith(prefix))
|
if (str.startsWith(prefix))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user