mirror of https://github.com/GeyserMC/Geyser.git
157 lines
7.2 KiB
Java
157 lines
7.2 KiB
Java
/*
|
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* @author GeyserMC
|
|
* @link https://github.com/GeyserMC/Geyser
|
|
*/
|
|
|
|
package org.geysermc.geyser.registry.loader;
|
|
|
|
import org.geysermc.geyser.GeyserImpl;
|
|
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
|
import org.geysermc.geyser.pack.GeyserResourcePack;
|
|
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
|
|
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
|
import org.geysermc.geyser.text.GeyserLocale;
|
|
import org.geysermc.geyser.util.FileUtils;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.FileSystems;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.PathMatcher;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipFile;
|
|
|
|
/**
|
|
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
|
|
*/
|
|
public class ResourcePackLoader implements RegistryLoader<Path, Map<String, ResourcePack>> {
|
|
|
|
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
|
|
|
private static final boolean SHOW_RESOURCE_PACK_LENGTH_WARNING = Boolean.parseBoolean(System.getProperty("Geyser.ShowResourcePackLengthWarning", "true"));
|
|
|
|
/**
|
|
* Loop through the packs directory and locate valid resource pack files
|
|
*/
|
|
@Override
|
|
public Map<String, ResourcePack> load(Path directory) {
|
|
Map<String, ResourcePack> packMap = new HashMap<>();
|
|
|
|
if (!Files.exists(directory)) {
|
|
try {
|
|
Files.createDirectory(directory);
|
|
} catch (IOException e) {
|
|
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
|
}
|
|
}
|
|
|
|
List<Path> resourcePacks;
|
|
try (Stream<Path> stream = Files.walk(directory)) {
|
|
resourcePacks = stream.filter(PACK_MATCHER::matches)
|
|
.collect(Collectors.toCollection(ArrayList::new)); // toList() does not guarantee mutability
|
|
} catch (Exception e) {
|
|
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
|
|
|
// Ensure the event is fired even if there was an issue reading
|
|
// from our own resource pack directory. External projects may have
|
|
// resource packs located at different locations.
|
|
resourcePacks = new ArrayList<>();
|
|
}
|
|
|
|
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
|
GeyserImpl.getInstance().eventBus().fire(event);
|
|
|
|
for (Path path : event.resourcePacks()) {
|
|
try {
|
|
GeyserResourcePack pack = readPack(path);
|
|
packMap.put(pack.manifest().header().uuid().toString(), pack);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
return packMap;
|
|
}
|
|
|
|
/**
|
|
* Reads a resource pack at the given file. Also searches for a file in the same directory, with the same name
|
|
* but suffixed by ".key", containing the content key. If such file does not exist, no content key is stored.
|
|
*
|
|
* @param path the file to read from, in ZIP format
|
|
* @return a {@link ResourcePack} representation
|
|
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
|
|
*/
|
|
public static GeyserResourcePack readPack(Path path) throws IllegalArgumentException {
|
|
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
|
|
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
|
|
}
|
|
|
|
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
|
|
|
|
try (ZipFile zip = new ZipFile(path.toFile());
|
|
Stream<? extends ZipEntry> stream = zip.stream()) {
|
|
stream.forEach(x -> {
|
|
String name = x.getName();
|
|
if (SHOW_RESOURCE_PACK_LENGTH_WARNING && name.length() >= 80) {
|
|
GeyserImpl.getInstance().getLogger().warning("The resource pack " + path.getFileName()
|
|
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
|
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
|
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
|
}
|
|
if (name.contains("manifest.json")) {
|
|
try {
|
|
GeyserResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), GeyserResourcePackManifest.class);
|
|
if (manifest.header().uuid() != null) {
|
|
manifestReference.set(manifest);
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
});
|
|
|
|
GeyserResourcePackManifest manifest = manifestReference.get();
|
|
if (manifest == null) {
|
|
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
|
|
}
|
|
|
|
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
|
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
|
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
|
|
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
|
|
|
|
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
|
|
} catch (Exception e) {
|
|
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
|
|
}
|
|
}
|
|
}
|