添加 Modrinth 整合包导出 (#3716)
Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -42,17 +42,29 @@ public interface ModAdviser {
|
||||
}
|
||||
|
||||
List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
|
||||
"regex:(.*?)\\.log",
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Old Minecraft Launcher
|
||||
"launcher_accounts.json", "launcher_cef_log.txt", "launcher_log.txt", "launcher_msa_credentials.bin", "launcher_settings.json", "launcher_ui_state.json", "realms_persistence.json", "webcache2", "treatment_tags.json", // New Minecraft Launcher
|
||||
"clientId.txt", "PCL.ini", // Plain Craft Launcher
|
||||
"backup", "pack.json", "launcher.jar", "cache", "modpack.cfg", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
".fabric", ".mixin.out", // Fabric
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
"downloads", // Curse
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data", "CustomSkinLoader/caches" // Mods
|
||||
"regex:(.*?)\\.log",
|
||||
"regex:.*\\.dat_old$", "regex:.*\\.old$", // Backup files
|
||||
"regex:.*\\.BakaCoreInfo$", // BakaXL
|
||||
"regex:.*-natives",
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Old Minecraft Launcher
|
||||
"launcher_accounts.json", "launcher_cef_log.txt", "launcher_log.txt", "launcher_msa_credentials.bin", "launcher_settings.json", "launcher_ui_state.json", "realms_persistence.json", "webcache2", "treatment_tags.json", // New Minecraft Launcher
|
||||
"clientId.txt", "PCL.ini", // Plain Craft Launcher
|
||||
"backup", "pack.json", "launcher.jar", "cache", "modpack.cfg", "log4j2.xml", "hmclversion.cfg", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
"modrinth.index.json", // Modrinth
|
||||
".fabric", ".mixin.out", ".optifine", // Fabric/OptiFine
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "$natives", "server-resource-packs", "command_history.txt", // Minecraft
|
||||
"downloads", "essential", // Downloads and Essential
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data", "CustomSkinLoader/caches", // Mods
|
||||
"debug", // Debug files
|
||||
".replay_cache", "replay_recordings", "replay_videos", // ReplayMod
|
||||
"irisUpdateInfo.json", // Iris
|
||||
"modernfix", // ModernFix
|
||||
"modtranslations", // Mod translations
|
||||
"schematics", // Schematics mod
|
||||
"journeymap/data", // JourneyMap
|
||||
"mods/.connector" // Sinytra Connector
|
||||
);
|
||||
|
||||
List<String> MODPACK_SUGGESTED_BLACK_LIST = Lang.immutableListOf(
|
||||
|
||||
@@ -29,6 +29,9 @@ public class ModpackExportInfo {
|
||||
|
||||
private List<McbbsModpackManifest.Origin> origins = new ArrayList<>();
|
||||
|
||||
private boolean noCreateRemoteFiles;
|
||||
private boolean skipCurseForgeRemoteFiles;
|
||||
|
||||
public ModpackExportInfo() {}
|
||||
|
||||
public List<String> getWhitelist() {
|
||||
@@ -186,6 +189,22 @@ public class ModpackExportInfo {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isNoCreateRemoteFiles() {
|
||||
return noCreateRemoteFiles;
|
||||
}
|
||||
|
||||
public void setNoCreateRemoteFiles(boolean noCreateRemoteFiles) {
|
||||
this.noCreateRemoteFiles = noCreateRemoteFiles;
|
||||
}
|
||||
|
||||
public boolean isSkipCurseForgeRemoteFiles() {
|
||||
return skipCurseForgeRemoteFiles;
|
||||
}
|
||||
|
||||
public void setSkipCurseForgeRemoteFiles(boolean skipCurseForgeRemoteFiles) {
|
||||
this.skipCurseForgeRemoteFiles = skipCurseForgeRemoteFiles;
|
||||
}
|
||||
|
||||
public ModpackExportInfo validate() throws NullPointerException {
|
||||
return this;
|
||||
}
|
||||
@@ -200,6 +219,8 @@ public class ModpackExportInfo {
|
||||
private boolean requireLaunchArguments;
|
||||
private boolean requireJavaArguments;
|
||||
private boolean requireOrigins;
|
||||
private boolean requireNoCreateRemoteFiles;
|
||||
private boolean requireSkipCurseForgeRemoteFiles;
|
||||
|
||||
public Options() {
|
||||
}
|
||||
@@ -240,6 +261,14 @@ public class ModpackExportInfo {
|
||||
return requireOrigins;
|
||||
}
|
||||
|
||||
public boolean isRequireNoCreateRemoteFiles() {
|
||||
return requireNoCreateRemoteFiles;
|
||||
}
|
||||
|
||||
public boolean isRequireSkipCurseForgeRemoteFiles() {
|
||||
return requireSkipCurseForgeRemoteFiles;
|
||||
}
|
||||
|
||||
public Options requireUrl() {
|
||||
requireUrl = true;
|
||||
return this;
|
||||
@@ -281,5 +310,14 @@ public class ModpackExportInfo {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Options requireNoCreateRemoteFiles() {
|
||||
requireNoCreateRemoteFiles = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Options requireSkipCurseForgeRemoteFiles() {
|
||||
requireSkipCurseForgeRemoteFiles = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.jackhuang.hmcl.mod.modrinth;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackExportInfo;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModrinthModpackExportTask extends Task<Void> {
|
||||
private final DefaultGameRepository repository;
|
||||
private final String version;
|
||||
private final ModpackExportInfo info;
|
||||
private final File modpackFile;
|
||||
|
||||
public ModrinthModpackExportTask(DefaultGameRepository repository, String version, ModpackExportInfo info, File modpackFile) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
this.info = info.validate();
|
||||
this.modpackFile = modpackFile;
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) modpackFile.delete();
|
||||
});
|
||||
}
|
||||
|
||||
private ModrinthManifest.File tryGetRemoteFile(Path file, String relativePath) throws IOException {
|
||||
if (info.isNoCreateRemoteFiles()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isDisabled = repository.getModManager(version).isDisabled(file);
|
||||
if (isDisabled) {
|
||||
relativePath = repository.getModManager(version).enableMod(Paths.get(relativePath)).toString();
|
||||
}
|
||||
|
||||
LocalModFile localModFile = null;
|
||||
Optional<RemoteMod.Version> modrinthVersion = Optional.empty();
|
||||
Optional<RemoteMod.Version> curseForgeVersion = Optional.empty();
|
||||
|
||||
try {
|
||||
modrinthVersion = ModrinthRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to get remote file from Modrinth for: " + file, e);
|
||||
}
|
||||
|
||||
if (!info.isSkipCurseForgeRemoteFiles() && CurseForgeRemoteModRepository.isAvailable()) {
|
||||
try {
|
||||
curseForgeVersion = CurseForgeRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to get remote file from CurseForge for: " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!modrinthVersion.isPresent() && !curseForgeVersion.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, String> hashes = new HashMap<>();
|
||||
hashes.put("sha1", DigestUtils.digestToString("SHA-1", file));
|
||||
hashes.put("sha512", DigestUtils.digestToString("SHA-512", file));
|
||||
|
||||
Map<String, String> env = null;
|
||||
if (isDisabled) {
|
||||
env = new HashMap<>();
|
||||
env.put("client", "optional");
|
||||
}
|
||||
|
||||
List<URL> downloads = new ArrayList<>();
|
||||
if (modrinthVersion.isPresent())
|
||||
downloads.add(new URL(modrinthVersion.get().getFile().getUrl()));
|
||||
if (curseForgeVersion.isPresent())
|
||||
downloads.add(new URL(curseForgeVersion.get().getFile().getUrl()));
|
||||
|
||||
long fileSize = Files.size(file);
|
||||
if (fileSize > Integer.MAX_VALUE) {
|
||||
LOG.warning("File " + relativePath + " is too large (size: " + fileSize + " bytes), precision may be lost when converting to int");
|
||||
}
|
||||
return new ModrinthManifest.File(
|
||||
relativePath,
|
||||
hashes,
|
||||
env,
|
||||
downloads,
|
||||
(int) fileSize
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);
|
||||
blackList.add(version + ".jar");
|
||||
blackList.add(version + ".json");
|
||||
LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
try (Zipper zip = new Zipper(modpackFile.toPath())) {
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath();
|
||||
List<ModrinthManifest.File> files = new ArrayList<>();
|
||||
Set<String> filesInManifest = new HashSet<>();
|
||||
|
||||
String[] resourceDirs = {"resourcepacks", "shaderpacks", "mods"};
|
||||
for (String dir : resourceDirs) {
|
||||
Path dirPath = runDirectory.resolve(dir);
|
||||
if (Files.exists(dirPath)) {
|
||||
Files.walk(dirPath)
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
try {
|
||||
String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/');
|
||||
|
||||
if (!info.getWhitelist().contains(relativePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModrinthManifest.File fileEntry = tryGetRemoteFile(file, relativePath);
|
||||
if (fileEntry != null) {
|
||||
files.add(fileEntry);
|
||||
filesInManifest.add(relativePath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to process file: " + file, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
zip.putDirectory(runDirectory, "client-overrides", path -> {
|
||||
String relativePath = path.toString().replace(File.separatorChar, '/');
|
||||
if (filesInManifest.contains(relativePath)) {
|
||||
return false;
|
||||
}
|
||||
return Modpack.acceptFile(path, blackList, info.getWhitelist());
|
||||
});
|
||||
|
||||
String gameVersion = repository.getGameVersion(version)
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + version));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version), gameVersion);
|
||||
|
||||
Map<String, String> dependencies = new HashMap<>();
|
||||
dependencies.put("minecraft", gameVersion);
|
||||
|
||||
analyzer.getVersion(FORGE).ifPresent(forgeVersion ->
|
||||
dependencies.put("forge", forgeVersion));
|
||||
analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion ->
|
||||
dependencies.put("neoforge", neoForgeVersion));
|
||||
analyzer.getVersion(FABRIC).ifPresent(fabricVersion ->
|
||||
dependencies.put("fabric-loader", fabricVersion));
|
||||
analyzer.getVersion(QUILT).ifPresent(quiltVersion ->
|
||||
dependencies.put("quilt-loader", quiltVersion));
|
||||
|
||||
ModrinthManifest manifest = new ModrinthManifest(
|
||||
"minecraft",
|
||||
1,
|
||||
info.getVersion(),
|
||||
info.getName(),
|
||||
info.getDescription(),
|
||||
files,
|
||||
dependencies
|
||||
);
|
||||
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(manifest), "modrinth.index.json");
|
||||
}
|
||||
}
|
||||
|
||||
public static final ModpackExportInfo.Options OPTION = new ModpackExportInfo.Options()
|
||||
.requireNoCreateRemoteFiles()
|
||||
.requireSkipCurseForgeRemoteFiles();
|
||||
}
|
||||
Reference in New Issue
Block a user