Skip to content

Commit

Permalink
Add new resourcepacks detection system
Browse files Browse the repository at this point in the history
  • Loading branch information
lukebemish committed Dec 4, 2023
1 parent 0e5aaa7 commit 391d135
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ build
eclipse
run
runserver
runs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package dev.lukebemish.defaultresources.impl;

import com.google.common.collect.Sets;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.IoSupplier;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class AutoMetadataFilePackResources extends AutoMetadataPathPackResources {
private final ZipFileHolder zipFileHolder;
public AutoMetadataFilePackResources(String s, String prefix, Path path, PackType packType) {
super(s, prefix, path, packType);
this.zipFileHolder = new ZipFileHolder();
}

private String getPathFromLocation(PackType packType, ResourceLocation location) {
return String.format(Locale.ROOT, "%s/%s/%s", this.getPackFolderName(), location.getNamespace(), location.getPath());
}

public IoSupplier<InputStream> getResource(PackType packType, ResourceLocation location) {
return this.getResource(getPathFromLocation(packType, location));
}

@Nullable
private IoSupplier<InputStream> getResource(String resourcePath) {
var zipFile = this.zipFileHolder.getOrCreateZipFile();
if (zipFile == null) {
return null;
} else {
var entry = zipFile.getEntry(resourcePath);
return entry == null ? null : IoSupplier.create(zipFile, entry);
}
}

public Set<String> getNamespaces(PackType type) {
var zipFile = this.zipFileHolder.getOrCreateZipFile();
if (zipFile == null) {
return Set.of();
} else {
Set<String> set = Sets.newHashSet();
var prefix = getPackFolderName() + "/";

for (var entry : ofEnumeration(zipFile.entries())) {
var entryPath = entry.getName();
var namespace = "";
if (entryPath.startsWith(prefix)) {
var parts = entryPath.substring(prefix.length()).split("/");
if (parts.length != 0) {
namespace = parts[0];
}
}
if (!namespace.isEmpty()) {
if (ResourceLocation.isValidNamespace(namespace)) {
set.add(namespace);
} else {
DefaultResources.LOGGER.warn(AutoMetadataFilePackResources.class.getSimpleName()+": Non [a-z0-9_.-] character in namespace {} in pack {}, ignoring", namespace, this.path);
}
}
}

return set;
}
}

private static <T> Iterable<T> ofEnumeration(Enumeration<T> enumeration) {
return enumeration::asIterator;
}

public void listResources(PackType packType, String namespace, String path, PackResources.ResourceOutput resourceOutput) {
var zipFile = this.zipFileHolder.getOrCreateZipFile();
if (zipFile != null) {
var namespacePrefix = getPackFolderName() + "/" + namespace + "/";
var pathPrefix = namespacePrefix + path + "/";
for (var entry : ofEnumeration(zipFile.entries())) {
if (!entry.isDirectory()) {
var entryPath = entry.getName();
if (entryPath.startsWith(pathPrefix)) {
var location = ResourceLocation.tryBuild(namespace, entryPath.substring(namespacePrefix.length()));
if (location != null) {
resourceOutput.accept(location, IoSupplier.create(zipFile, entry));
} else {
DefaultResources.LOGGER.warn(AutoMetadataFilePackResources.class.getSimpleName()+": Invalid path in datapack: {}:{}, ignoring", namespace, entryPath);
}
}
}
}
}
}

private class ZipFileHolder implements AutoCloseable {
@Nullable
private ZipFile zipFile;
private boolean loaded;

@Nullable
ZipFile getOrCreateZipFile() {
if (zipFile == null && this.loaded) {
return null;
} else {
if (this.zipFile == null) {
this.loaded = true;
try {
this.zipFile = new ZipFile(AutoMetadataFilePackResources.this.path.toFile());
} catch (IOException var2) {
DefaultResources.LOGGER.error(AutoMetadataFilePackResources.class.getSimpleName()+": Failed to open pack {}", AutoMetadataFilePackResources.this.path, var2);
return null;
}
}

return this.zipFile;
}
}

public void close() {
if (this.zipFile != null) {
IOUtils.closeQuietly(this.zipFile);
this.zipFile = null;
}
}
}

@Override
public void close() {
super.close();
if (this.zipFileHolder != null) {
this.zipFileHolder.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class AutoMetadataPathPackResources extends AbstractPackResources {
private static final Logger LOGGER = LogUtils.getLogger();

private final String name;
private final Path path;
protected final Path path;
private final PackType packType;

public AutoMetadataPathPackResources(String s, String prefix, Path path, PackType packType) {
Expand All @@ -42,6 +42,10 @@ public AutoMetadataPathPackResources(String s, String prefix, Path path, PackTyp
this.packType = packType;
}

protected String getPackFolderName() {
return name;
}

@Nullable
@Override
public IoSupplier<InputStream> getRootResource(String... elements) {
Expand All @@ -51,7 +55,7 @@ public IoSupplier<InputStream> getRootResource(String... elements) {
@Nullable
@Override
public IoSupplier<InputStream> getResource(PackType packType, ResourceLocation location) {
Path path = this.path.resolve(name).resolve(location.getNamespace());
Path path = this.path.resolve(getPackFolderName()).resolve(location.getNamespace());
if (!Files.isDirectory(path)) {
return null;
}
Expand All @@ -61,7 +65,7 @@ public IoSupplier<InputStream> getResource(PackType packType, ResourceLocation l
@Override
public void listResources(PackType packType, String namespace, String path, ResourceOutput resourceOutput) {
FileUtil.decomposePath(path).get().ifLeft((list) -> {
Path namespacePath = this.path.resolve(name).resolve(namespace);
Path namespacePath = this.path.resolve(getPackFolderName()).resolve(namespace);
if (!Files.isDirectory(namespacePath)) {
return;
}
Expand All @@ -72,7 +76,7 @@ public void listResources(PackType packType, String namespace, String path, Reso
@Override
public @NonNull Set<String> getNamespaces(PackType type) {
Set<String> set = new HashSet<>();
Path path = this.path.resolve(name);
Path path = this.path.resolve(getPackFolderName());

if (!Files.isDirectory(path)) {
return Set.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.google.common.base.Suppliers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
Expand All @@ -15,22 +16,25 @@
import org.jspecify.annotations.NonNull;

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.ZipFile;

public record Config(HashMap<String, ExtractionState> extract) {
public record Config(HashMap<String, ExtractionState> extract, HashMap<String, Boolean> fromResourcePacks) {
public static final Codec<Config> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.unboundedMap(Codec.STRING, StringRepresentable.fromEnum(ExtractionState::values)).xmap(HashMap::new, Function.identity()).fieldOf("extract").forGetter(Config::extract)
Codec.unboundedMap(Codec.STRING, StringRepresentable.fromEnum(ExtractionState::values)).xmap(HashMap::new, Function.identity()).fieldOf("extract").forGetter(Config::extract),
Codec.unboundedMap(Codec.STRING, Codec.BOOL).fieldOf("from_resource_packs").xmap(HashMap::new, Function.identity()).forGetter(Config::fromResourcePacks)
).apply(i, Config::new));

public static final Supplier<Config> INSTANCE = Suppliers.memoize(Config::readFromConfig);

private static Config getDefault() {
return new Config(new HashMap<>());
return new Config(new HashMap<>(), new HashMap<>());
}

private static Config readFromConfig() {
Expand All @@ -55,7 +59,32 @@ private static Config readFromConfig() {
map.put(modId, ExtractionState.UNEXTRACTED);
}
});
config = new Config(map);

var resourcePacks = new HashMap<String, Boolean>();
var originalResourcePacks = config.fromResourcePacks();
Path resourcePacksPath = Services.PLATFORM.getResourcePackDir();
try (var paths = Files.list(resourcePacksPath)) {
paths.forEach(path -> {
String fileName = path.getFileName().toString();
boolean detect;
if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) {
detect = checkZipForMeta(path);
} else if (Files.isDirectory(path)) {
detect = checkPathForMeta(path);
} else {
return;
}
if (detect) {
resourcePacks.put(fileName, originalResourcePacks.getOrDefault(fileName, true));
}
});
} catch (IOException e) {
if (Files.exists(resourcePacksPath)) {
DefaultResources.LOGGER.warn("Could not read resource packs from {}!", resourcePacksPath, e);
}
}

config = new Config(map, resourcePacks);

try {
writeConfig(configPath, config);
Expand All @@ -66,6 +95,42 @@ private static Config readFromConfig() {
return config;
}

private static boolean checkZipForMeta(Path path) {
try (var zipFile = new ZipFile(path.toFile());
var reader = zipFile.getInputStream(zipFile.getEntry("pack.mcmeta"))) {
if (reader != null) {
JsonObject object = DefaultResources.GSON.fromJson(new InputStreamReader(reader), JsonObject.class);
JsonElement meta = object.get(DefaultResources.MOD_ID);
var result = DefaultResourcesMetadataSection.CODEC.parse(JsonOps.INSTANCE, meta);
if (result.error().isPresent()) {
DefaultResources.LOGGER.error("Could not read metadata of {} for resource pack detection; ignoring: {}", path.getFileName(), result.error().get());
}
return result.result().isPresent() && result.result().get().detect();
}
} catch (Exception e) {
DefaultResources.LOGGER.error("Could not read {}, which looks like a zip file, for resource pack detection; ignoring.", path.getFileName(), e);
}
return false;
}

private static boolean checkPathForMeta(Path path) {
var metaFile = path.resolve("pack.mcmeta");
if (Files.exists(metaFile)) {
try (var reader = Files.newBufferedReader(metaFile)) {
JsonObject object = DefaultResources.GSON.fromJson(reader, JsonObject.class);
JsonElement meta = object.get(DefaultResources.MOD_ID);
var result = DefaultResourcesMetadataSection.CODEC.parse(JsonOps.INSTANCE, meta);
if (result.error().isPresent()) {
DefaultResources.LOGGER.error("Could not read metadata of {} for resource pack detection; ignoring: {}", path.getFileName(), result.error().get());
}
return result.result().isPresent() && result.result().get().detect();
} catch (Exception e) {
DefaultResources.LOGGER.error("Could not read {} for resource pack detection; ignoring.", path.getFileName(), e);
}
}
return false;
}

private static void writeConfig(Path path, Config config) throws IOException {
if (!Files.exists(path.getParent()))
Files.createDirectories(path.getParent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ private static List<Pair<String, Pack.ResourcesSupplier>> getStaticPackResources
} catch (IOException ignored) {

}
packs.addAll(getDetectedPacks(type));
QUEUED_STATIC_RESOURCES.forEach((s, biFunction) -> {
Supplier<PackResources> resources = biFunction.apply(s, type);
if (resources == null) return;
Expand All @@ -174,6 +175,24 @@ private static List<Pair<String, Pack.ResourcesSupplier>> getStaticPackResources
return packs;
}

private static List<Pair<String, Pack.ResourcesSupplier>> getDetectedPacks(PackType type) {
List<Pair<String, Pack.ResourcesSupplier>> packs = new ArrayList<>();
Config.INSTANCE.get().fromResourcePacks().forEach((name, enabled) -> {
if (enabled) {
Path path = Services.PLATFORM.getResourcePackDir().resolve(name);
if (Files.isDirectory(path)) {
packs.add(Pair.of(name, wrap(n -> new AutoMetadataPathPackResources(n, GLOBAL_PREFIX, path, type))));
} else if (Files.isRegularFile(path)) {
packs.add(Pair.of(name, wrap(n -> new AutoMetadataFilePackResources(n, GLOBAL_PREFIX, path, type))));
} else {
return;
}
DefaultResources.LOGGER.info("Added resource pack \"{}\" to global {} resource providers", name, type.getDirectory());
}
});
return packs;
}

private volatile static boolean GLOBAL_SETUP = false;

public synchronized static void initialize() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Luke Bemish, and contributors
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

package dev.lukebemish.defaultresources.impl;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;

public record DefaultResourcesMetadataSection(boolean detect) {
public static final Codec<DefaultResourcesMetadataSection> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.BOOL.optionalFieldOf("detect", false).forGetter(DefaultResourcesMetadataSection::detect)
).apply(i, DefaultResourcesMetadataSection::new));
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface Platform {
Collection<Pair<String, Pack.ResourcesSupplier>> getJarProviders(PackType type);

Path getConfigDir();
Path getResourcePackDir();

Map<String, Path> getExistingModdedPaths(String relative);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public Path getConfigDir() {
return FabricLoader.getInstance().getConfigDir();
}

@Override
public Path getResourcePackDir() {
return FabricLoader.getInstance().getGameDir().resolve("resourcepacks");
}

@Override
public Map<String, Path> getExistingModdedPaths(String relative) {
Map<String, Path> out = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public Path getConfigDir() {
return FMLPaths.CONFIGDIR.get();
}

@Override
public Path getResourcePackDir() {
return FMLPaths.GAMEDIR.get().resolve("resourcepacks");
}

@Override
public Map<String, Path> getExistingModdedPaths(String relative) {
return FMLLoader.getLoadingModList().getModFiles().stream().flatMap(f -> f.getMods().stream())
Expand Down
Loading

0 comments on commit 391d135

Please sign in to comment.