Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ build/
.kotlin

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/**/*
*.iws
*.iml
*.ipr
Expand Down
3 changes: 0 additions & 3 deletions .idea/.gitignore

This file was deleted.

1 change: 0 additions & 1 deletion .idea/.name

This file was deleted.

9 changes: 0 additions & 9 deletions .idea/dictionaries/project.xml

This file was deleted.

16 changes: 0 additions & 16 deletions .idea/gradle.xml

This file was deleted.

12 changes: 0 additions & 12 deletions .idea/material_theme_project_new.xml

This file was deleted.

7 changes: 0 additions & 7 deletions .idea/misc.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

99 changes: 99 additions & 0 deletions src/main/java/org/ceciliastudio/modpackconverter/Converter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.ceciliastudio.modpackconverter;

import org.ceciliastudio.modpackconverter.util.FileUtil;
import org.ceciliastudio.modpackconverter.util.ZipUtil;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

public class Converter {
@SuppressWarnings("SpellCheckingInspection")
private static final List<String> IGNORED_FILES = List.of(
// Launchers
"PCL", "hmclversion.cfg", ".PCL_Mac.json",
// Generated at runtime
"logs", "crash-reports", "screenshots", "backups", "command_history.txt", "usercache.json", ".fabric", "saves"
);

private static Optional<Path> findInstanceRoot(Path modpackRoot) throws IOException {
if (!Files.isDirectory(modpackRoot)) return Optional.empty();
try (Stream<Path> stream = Files.walk(modpackRoot, 3)) {
return stream.filter(path -> {
if (!Files.isDirectory(path)) return false;
String fileName = path.getFileName().toString();
return Files.exists(path.resolve(fileName + ".json"))
&& Files.exists(path.resolve(fileName + ".jar"));
}).findFirst();
}
}

@SuppressWarnings("SameParameterValue")
private static void generateModrinthManifest(String name, String versionId, String summary, Map<String, String> dependencies, Path destination) throws IOException {
StringBuilder dependenciesString = new StringBuilder();
dependencies.forEach((key, value) -> dependenciesString.append('"').append(key).append('"').append(": ")
.append('"').append(value).append('"'));
String content = "{ \"formatVersion\": 1, \"game\": \"minecraft\", \"name\": \"%s\", \"versionId\": \"%s\", \"summary\": \"%s\", \"files\": [], \"dependencies\": { %s } }".formatted(name, versionId, summary, dependenciesString.toString());
Files.writeString(destination, content);
Comment thread
AnemoFlower marked this conversation as resolved.
}

private static void copyInstanceFiles(Path source, Path destination) throws IOException {
try (Stream<Path> stream = Files.list(source)) {
stream.forEach(path -> {
if (IGNORED_FILES.contains(path.getFileName().toString()) || path.getFileName().toString().startsWith("natives")) return;
try {
FileUtil.copy(path, destination);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}

public static void convert(Path modpackPath, Path destination) throws IOException {
Path tempDirectory = Files.createTempDirectory("modpackconverter");
Path mrpackDirectory = Files.createDirectory(tempDirectory.resolve("mrpack"));
String name = modpackPath.getFileName().toString().replaceFirst("\\.[^.]+$", "");
try {
System.out.println("正在解压整合包文件……");
ZipUtil.unzip(modpackPath, tempDirectory);
System.out.println("正在查找实例……");
Optional<Path> instanceRoot = findInstanceRoot(tempDirectory);
if (instanceRoot.isEmpty()) {
System.err.println("未找到符合要求的实例目录");
throw new RuntimeException("未找到符合要求的实例目录。");
}
System.out.println("正在生成 Modrinth 整合包……");
generateModrinthManifest(name, "未知", "未知", Map.of("minecraft", "1.21"), mrpackDirectory.resolve("modrinth.index.json"));
System.out.println("正在拷贝文件……");
copyInstanceFiles(instanceRoot.get(), mrpackDirectory.resolve("overrides"));
System.out.println("正在创建压缩包……");
ZipUtil.zip(mrpackDirectory, destination);
System.out.println("整合包转换完成");
} finally {
Files.walkFileTree(tempDirectory, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}

private Converter() {
}
}
5 changes: 4 additions & 1 deletion src/main/java/org/ceciliastudio/modpackconverter/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.ceciliastudio.modpackconverter;

import java.io.IOException;
import java.nio.file.Paths;
Comment thread
AnemoFlower marked this conversation as resolved.
Outdated

public class Main {
public static void main(String[] args) {
public static void main(String[] args) throws IOException {
System.out.println("Hello, world!");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.ceciliastudio.modpackconverter.util;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class FileUtil {
/**
* 拷贝文件或目录到目标目录内。
*
* @param path 要拷贝的文件或目录路径。
* @param destination 目标目录,path 的内容会作为子项放在该目录下。
* @throws IOException 发生 I/O 错误时抛出。
*/
public static void copy(Path path, Path destination) throws IOException {
if (Files.isDirectory(path)) {
Path targetDir = destination.resolve(path.getFileName());
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path rel = path.relativize(dir);
Path target = targetDir.resolve(rel);
if (Files.notExists(target)) {
Files.createDirectories(target);
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path rel = path.relativize(file);
Path target = targetDir.resolve(rel);
Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
} else {
if (Files.notExists(destination)) {
Files.createDirectories(destination);
}
Path target = destination.resolve(path.getFileName());
Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
84 changes: 84 additions & 0 deletions src/main/java/org/ceciliastudio/modpackconverter/util/ZipUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.ceciliastudio.modpackconverter.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
Comment thread
AnemoFlower marked this conversation as resolved.
Outdated
import java.util.zip.ZipOutputStream;

public class ZipUtil {
/**
* 压缩指定目录下的所有文件和子目录(不包含该目录本身)。
*
* @param contentRoot 要压缩的目录,仅包含其内部的内容,根目录自身不会包含在归档中。
* @param destination 生成的压缩包路径。
* @throws IOException 发生 I/O 错误时抛出。
*/
public static void zip(Path contentRoot, Path destination) throws IOException {
try (OutputStream fos = Files.newOutputStream(destination);
ZipOutputStream zos = new ZipOutputStream(fos)) {
Files.walkFileTree(contentRoot, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
Path relativePath = contentRoot.relativize(file);
zos.putNextEntry(new ZipEntry(relativePath.toString().replace("\\", "/")));
Files.copy(file, zos);
zos.closeEntry();
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attributes) throws IOException {
if (!contentRoot.equals(dir)) {
Path relativePath = contentRoot.relativize(dir).resolve("");
zos.putNextEntry(new ZipEntry(relativePath.toString().replace("\\", "/") + "/"));
zos.closeEntry();
}
return FileVisitResult.CONTINUE;
}
});
}
}

/**
* 解压 ZIP 压缩包到指定目录,会自动创建目标目录(如果不存在)。
* 解压内容为压缩包内所有文件和子目录,保持原有目录结构。
*
* @param archivePath ZIP 压缩包路径。
* @param destination 目标解压目录,不存在时会自动创建。
* @throws IOException 发生 I/O 错误时抛出。
*/
public static void unzip(Path archivePath, Path destination) throws IOException {
if (Files.notExists(destination)) {
Files.createDirectories(destination);
}
try (ZipFile zipFile = new ZipFile(archivePath.toFile())) {
zipFile.stream().forEach(entry -> {
try {
Path outPath = destination.resolve(entry.getName()).normalize();
if (!outPath.startsWith(destination)) {
throw new IOException("Entry is outside of the target dir: " + entry.getName());
}
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (InputStream in = zipFile.getInputStream(entry)) {
Files.copy(in, outPath, StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
Comment thread
AnemoFlower marked this conversation as resolved.

private ZipUtil() {
}
}
Loading