feat: 资源包管理 (#4475)
Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
@@ -564,4 +564,8 @@ public class DefaultGameRepository implements GameRepository {
|
||||
.append("baseDirectory", baseDirectory)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public Path getResourcepacksDirectory(String id) {
|
||||
return getRunDirectory(id).resolve("resourcepacks");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public class Datapack {
|
||||
private Optional<Pack> parsePack(Path datapackPath, boolean isDirectory, String name, Path mcmetaPath) {
|
||||
try {
|
||||
PackMcMeta mcMeta = JsonUtils.fromNonNullJson(Files.readString(mcmetaPath), PackMcMeta.class);
|
||||
return Optional.of(new Pack(datapackPath, isDirectory, name, mcMeta.getPackInfo().getDescription(), this));
|
||||
return Optional.of(new Pack(datapackPath, isDirectory, name, mcMeta.pack().description(), this));
|
||||
} catch (JsonParseException e) {
|
||||
LOG.warning("Invalid pack.mcmeta format in " + datapackPath, e);
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -23,9 +23,9 @@ import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -36,28 +36,12 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@Immutable
|
||||
public class PackMcMeta implements Validation {
|
||||
@SerializedName("pack")
|
||||
private final PackInfo pack;
|
||||
|
||||
public PackMcMeta() {
|
||||
this(new PackInfo());
|
||||
}
|
||||
|
||||
public PackMcMeta(PackInfo packInfo) {
|
||||
this.pack = packInfo;
|
||||
}
|
||||
|
||||
public PackInfo getPackInfo() {
|
||||
return pack;
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Validation {
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (pack == null)
|
||||
@@ -65,29 +49,10 @@ public class PackMcMeta implements Validation {
|
||||
}
|
||||
|
||||
@JsonAdapter(PackInfoDeserializer.class)
|
||||
public static class PackInfo {
|
||||
@SerializedName("pack_format")
|
||||
private final int packFormat;
|
||||
|
||||
@SerializedName("min_format")
|
||||
private final PackVersion minPackVersion;
|
||||
@SerializedName("max_format")
|
||||
private final PackVersion maxPackVersion;
|
||||
|
||||
@SerializedName("description")
|
||||
private final LocalModFile.Description description;
|
||||
|
||||
public PackInfo() {
|
||||
this(0, PackVersion.UNSPECIFIED, PackVersion.UNSPECIFIED, new LocalModFile.Description(Collections.emptyList()));
|
||||
}
|
||||
|
||||
public PackInfo(int packFormat, PackVersion minPackVersion, PackVersion maxPackVersion, LocalModFile.Description description) {
|
||||
this.packFormat = packFormat;
|
||||
this.minPackVersion = minPackVersion;
|
||||
this.maxPackVersion = maxPackVersion;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public record PackInfo(@SerializedName("pack_format") int packFormat,
|
||||
@SerializedName("min_format") PackVersion minPackVersion,
|
||||
@SerializedName("max_format") PackVersion maxPackVersion,
|
||||
@SerializedName("description") LocalModFile.Description description) {
|
||||
public PackVersion getEffectiveMinVersion() {
|
||||
return !minPackVersion.isUnspecified() ? minPackVersion : new PackVersion(packFormat, 0);
|
||||
}
|
||||
@@ -95,10 +60,6 @@ public class PackMcMeta implements Validation {
|
||||
public PackVersion getEffectiveMaxVersion() {
|
||||
return !maxPackVersion.isUnspecified() ? maxPackVersion : new PackVersion(packFormat, 0);
|
||||
}
|
||||
|
||||
public LocalModFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public record PackVersion(int majorVersion, int minorVersion) implements Comparable<PackVersion> {
|
||||
@@ -148,7 +109,7 @@ public class PackMcMeta implements Validation {
|
||||
}
|
||||
}
|
||||
|
||||
public static class PackInfoDeserializer implements JsonDeserializer<PackInfo> {
|
||||
public static final class PackInfoDeserializer implements JsonDeserializer<PackInfo> {
|
||||
|
||||
private List<LocalModFile.Description.Part> pairToPart(List<Pair<String, String>> lists, String color) {
|
||||
List<LocalModFile.Description.Part> parts = new ArrayList<>();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
public interface ResourcepackFile {
|
||||
@Nullable
|
||||
LocalModFile.Description getDescription();
|
||||
|
||||
String getName();
|
||||
|
||||
Path getPath();
|
||||
|
||||
byte @Nullable [] getIcon();
|
||||
|
||||
static ResourcepackFile parse(Path path) throws IOException {
|
||||
String fileName = path.getFileName().toString();
|
||||
if (Files.isRegularFile(path) && fileName.toLowerCase(Locale.ROOT).endsWith(".zip")) {
|
||||
return new ResourcepackZipFile(path);
|
||||
} else if (Files.isDirectory(path) && Files.exists(path.resolve("pack.mcmeta"))) {
|
||||
return new ResourcepackFolder(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackFolder implements ResourcepackFile {
|
||||
private final Path path;
|
||||
private final LocalModFile.Description description;
|
||||
private final byte @Nullable [] icon;
|
||||
|
||||
public ResourcepackFolder(Path path) {
|
||||
this.path = path;
|
||||
|
||||
LocalModFile.Description description = null;
|
||||
try {
|
||||
description = JsonUtils.fromJsonFile(path.resolve("pack.mcmeta"), PackMcMeta.class).pack().description();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resourcepack meta", e);
|
||||
}
|
||||
|
||||
byte[] icon;
|
||||
try {
|
||||
icon = Files.readAllBytes(path.resolve("pack.png"));
|
||||
} catch (IOException e) {
|
||||
icon = null;
|
||||
LOG.warning("Failed to read resourcepack icon", e);
|
||||
}
|
||||
this.icon = icon;
|
||||
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalModFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] getIcon() {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ZipFileTree;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackZipFile implements ResourcepackFile {
|
||||
private final Path path;
|
||||
private final byte @Nullable [] icon;
|
||||
private final String name;
|
||||
private final LocalModFile.Description description;
|
||||
|
||||
public ResourcepackZipFile(Path path) throws IOException {
|
||||
this.path = path;
|
||||
LocalModFile.Description description = null;
|
||||
|
||||
byte[] icon = null;
|
||||
|
||||
try (var zipFileTree = new ZipFileTree(CompressingUtils.openZipFile(path))) {
|
||||
try {
|
||||
description = JsonUtils.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta"), PackMcMeta.class).pack().description();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resourcepack meta", e);
|
||||
}
|
||||
|
||||
var iconEntry = zipFileTree.getEntry("/pack.png");
|
||||
if (iconEntry != null) {
|
||||
try (InputStream is = zipFileTree.getInputStream(iconEntry)) {
|
||||
icon = is.readAllBytes();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resourcepack icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
|
||||
name = FileUtils.getNameWithoutExtension(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalModFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] getIcon() {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user