Exposing resourcepack loading to api (#3696)

Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com>
Co-authored-by: RednedEpic <redned235@gmail.com>
This commit is contained in:
chris 2023-06-17 03:39:53 +02:00 committed by GitHub
parent 6eca6ade06
commit 903e61f1a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1334 additions and 347 deletions

View file

@ -36,6 +36,7 @@ import org.geysermc.geyser.api.extension.ExtensionManager;
import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.network.RemoteServer;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -107,6 +108,22 @@ public interface GeyserApi extends GeyserApiBase {
@NonNull @NonNull
BedrockListener bedrockListener(); BedrockListener bedrockListener();
/**
* Gets the {@link Path} to the Geyser config directory.
*
* @return the path to the Geyser config directory
*/
@NonNull
Path configDirectory();
/**
* Gets the {@link Path} to the Geyser packs directory.
*
* @return the path to the Geyser packs directory
*/
@NonNull
Path packDirectory();
/** /**
* Gets the current {@link GeyserApiBase} instance. * Gets the current {@link GeyserApiBase} instance.
* *

View file

@ -0,0 +1,67 @@
/*
* 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.api.event.bedrock;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
import java.util.List;
import java.util.UUID;
/**
* Called when Geyser initializes a session for a new Bedrock client and is in the process of sending resource packs.
*/
public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
public SessionLoadResourcePacksEvent(@NonNull GeyserConnection connection) {
super(connection);
}
/**
* Gets an unmodifiable list of {@link ResourcePack}s that will be sent to the client.
*
* @return an unmodifiable list of resource packs that will be sent to the client.
*/
public abstract @NonNull List<ResourcePack> resourcePacks();
/**
* Registers a {@link ResourcePack} to be sent to the client.
*
* @param resourcePack a resource pack that will be sent to the client.
* @return true if the resource pack was added successfully,
* or false if already present
*/
public abstract boolean register(@NonNull ResourcePack resourcePack);
/**
* Unregisters a resource pack from being sent to the client.
*
* @param uuid the UUID of the resource pack
* @return true whether the resource pack was removed from the list of resource packs.
*/
public abstract boolean unregister(@NonNull UUID uuid);
}

View file

@ -0,0 +1,82 @@
/*
* 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.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
/**
* Represents a pack codec that can be used
* to provide resource packs to clients.
*/
public abstract class PackCodec {
/**
* Gets the sha256 hash of the resource pack.
*
* @return the hash of the resource pack
*/
public abstract byte @NonNull [] sha256();
/**
* Gets the resource pack size.
*
* @return the resource pack file size
*/
public abstract long size();
/**
* Serializes the given resource pack into a byte buffer.
*
* @param resourcePack the resource pack to serialize
* @return the serialized resource pack
*/
@NonNull
public abstract SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException;
/**
* Creates a new resource pack from this codec.
*
* @return the new resource pack
*/
@NonNull
protected abstract ResourcePack create();
/**
* Creates a new pack provider from the given path.
*
* @param path the path to create the pack provider from
* @return the new pack provider
*/
@NonNull
public static PackCodec path(@NonNull Path path) {
return GeyserApi.api().provider(PathPackCodec.class, path);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.nio.file.Path;
/**
* Represents a pack codec that creates a resource
* pack from a path on the filesystem.
*/
public abstract class PathPackCodec extends PackCodec {
/**
* Gets the path of the resource pack.
*
* @return the path of the resource pack
*/
@NonNull
public abstract Path path();
}

View file

@ -0,0 +1,72 @@
/*
* 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.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a resource pack sent to Bedrock clients
* <p>
* This representation of a resource pack only contains what
* Geyser requires to send it to the client.
*/
public interface ResourcePack {
/**
* The {@link PackCodec codec} for this pack.
*
* @return the codec for this pack
*/
@NonNull
PackCodec codec();
/**
* Gets the resource pack manifest.
*
* @return the resource pack manifest
*/
@NonNull
ResourcePackManifest manifest();
/**
* Gets the content key of the resource pack. Lack of a content key is represented by an empty String.
*
* @return the content key of the resource pack
*/
@NonNull
String contentKey();
/**
* Creates a resource pack with the given {@link PackCodec}.
*
* @param codec the pack codec
* @return the resource pack
*/
@NonNull
static ResourcePack create(@NonNull PackCodec codec) {
return codec.create();
}
}

View file

@ -0,0 +1,209 @@
/*
* 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.api.pack;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.UUID;
/**
* Represents a resource pack manifest.
*/
public interface ResourcePackManifest {
/**
* Gets the format version of the resource pack.
*
* @return the format version
*/
int formatVersion();
/**
* Gets the header of the resource pack.
*
* @return the header
*/
@NonNull
Header header();
/**
* Gets the modules of the resource pack.
*
* @return the modules
*/
@NonNull
Collection<? extends Module> modules();
/**
* Gets the dependencies of the resource pack.
*
* @return the dependencies
*/
@NonNull
Collection<? extends Dependency> dependencies();
/**
* Represents the header of a resource pack.
*/
interface Header {
/**
* Gets the UUID of the resource pack.
*
* @return the UUID
*/
@NonNull
UUID uuid();
/**
* Gets the version of the resource pack.
*
* @return the version
*/
@NonNull
Version version();
/**
* Gets the name of the resource pack.
*
* @return the name
*/
@NonNull
String name();
/**
* Gets the description of the resource pack.
*
* @return the description
*/
@NonNull
String description();
/**
* Gets the minimum supported Minecraft version of the resource pack.
*
* @return the minimum supported Minecraft version
*/
@NonNull
Version minimumSupportedMinecraftVersion();
}
/**
* Represents a module of a resource pack.
*/
interface Module {
/**
* Gets the UUID of the module.
*
* @return the UUID
*/
@NonNull
UUID uuid();
/**
* Gets the version of the module.
*
* @return the version
*/
@NonNull
Version version();
/**
* Gets the type of the module.
*
* @return the type
*/
@NonNull
String type();
/**
* Gets the description of the module.
*
* @return the description
*/
@NonNull
String description();
}
/**
* Represents a dependency of a resource pack.
*/
interface Dependency {
/**
* Gets the UUID of the dependency.
*
* @return the uuid
*/
@NonNull
UUID uuid();
/**
* Gets the version of the dependency.
*
* @return the version
*/
@NonNull
Version version();
}
/**
* Represents a version of a resource pack.
*/
interface Version {
/**
* Gets the major version.
*
* @return the major version
*/
int major();
/**
* Gets the minor version.
*
* @return the minor version
*/
int minor();
/**
* Gets the patch version.
*
* @return the patch version
*/
int patch();
/**
* Gets the version formatted as a String.
*
* @return the version string
*/
@NonNull String toString();
}
}

View file

@ -30,7 +30,7 @@ dependencies {
} }
implementation(libs.raknet) { implementation(libs.raknet) {
exclude("io.netty", "*"); exclude("io.netty", "*")
} }
implementation(libs.netty.resolver.dns) implementation(libs.netty.resolver.dns)

View file

@ -69,9 +69,9 @@ import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.netty.GeyserServer; import org.geysermc.geyser.network.netty.GeyserServer;
import org.geysermc.geyser.pack.ResourcePack;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.loader.RegistryLoaders;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
@ -90,6 +90,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.file.Path;
import java.security.Key; import java.security.Key;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.*; import java.util.*;
@ -258,7 +259,7 @@ public class GeyserImpl implements GeyserApi {
SkinProvider.registerCacheImageTask(this); SkinProvider.registerCacheImageTask(this);
ResourcePack.loadPacks(); Registries.RESOURCE_PACKS.load();
String geyserUdpPort = System.getProperty("geyserUdpPort", ""); String geyserUdpPort = System.getProperty("geyserUdpPort", "");
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort; String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
@ -622,7 +623,7 @@ public class GeyserImpl implements GeyserApi {
this.erosionUnixListener.close(); this.erosionUnixListener.close();
} }
ResourcePack.PACKS.clear(); Registries.RESOURCE_PACKS.get().clear();
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
this.extensionManager.disableExtensions(); this.extensionManager.disableExtensions();
@ -681,6 +682,18 @@ public class GeyserImpl implements GeyserApi {
return getConfig().getBedrock(); return getConfig().getBedrock();
} }
@Override
@NonNull
public Path configDirectory() {
return bootstrap.getConfigFolder();
}
@Override
@NonNull
public Path packDirectory() {
return bootstrap.getConfigFolder().resolve("packs");
}
public int buildNumber() { public int buildNumber() {
if (!this.isProductionEnvironment()) { if (!this.isProductionEnvironment()) {
return 0; return 0;

View file

@ -0,0 +1,69 @@
/*
* 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.event.type;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.session.GeyserSession;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
private final Map<String, ResourcePack> packs;
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
super(session);
this.packs = packMap;
}
public @NonNull Map<String, ResourcePack> getPacks() {
return packs;
}
@Override
public @NonNull List<ResourcePack> resourcePacks() {
return List.copyOf(packs.values());
}
@Override
public boolean register(@NonNull ResourcePack resourcePack) {
String packID = resourcePack.manifest().header().uuid().toString();
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
return false;
}
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
return true;
}
@Override
public boolean unregister(@NonNull UUID uuid) {
return packs.remove(uuid.toString()) != null;
}
}

View file

@ -49,9 +49,12 @@ import org.cloudburstmc.protocol.common.PacketSignal;
import org.geysermc.geyser.Constants; import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.pack.ResourcePack; import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
import org.geysermc.geyser.pack.ResourcePackManifest; import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
@ -61,16 +64,20 @@ import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils; import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.VersionCheckUtils;
import java.io.FileInputStream; import java.io.IOException;
import java.io.InputStream; import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap;
import java.util.OptionalInt; import java.util.OptionalInt;
public class UpstreamPacketHandler extends LoggingPacketHandler { public class UpstreamPacketHandler extends LoggingPacketHandler {
private boolean networkSettingsRequested = false; private boolean networkSettingsRequested = false;
private Deque<String> packsToSent = new ArrayDeque<>(); private final Deque<String> packsToSent = new ArrayDeque<>();
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, session); super(geyser, session);
@ -172,12 +179,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
geyser.getSessionManager().addPendingSession(session); geyser.getSessionManager().addPendingSession(session);
this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()));
this.geyser.eventBus().fire(this.resourcePackLoadEvent);
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header();
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
resourcePack.getContentKey(), "", header.getUuid().toString(), false, false)); "", header.uuid().toString(), false, false));
} }
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo); session.sendUpstreamPacket(resourcePacksInfo);
@ -210,9 +221,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
stackPacket.setGameVersion(session.getClientData().getGameVersion()); stackPacket.setGameVersion(session.getClientData().getGameVersion());
for (ResourcePack pack : ResourcePack.PACKS.values()) { for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
ResourcePackManifest.Header header = pack.getManifest().getHeader(); ResourcePackManifest.Header header = pack.manifest().header();
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), ""));
} }
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
@ -291,21 +302,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override @Override
public PacketSignal handle(ResourcePackChunkRequestPacket packet) { public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString()); ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString());
PackCodec codec = pack.codec();
data.setChunkIndex(packet.getChunkIndex()); data.setChunkIndex(packet.getChunkIndex());
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE); data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
data.setPackVersion(packet.getPackVersion()); data.setPackVersion(packet.getPackVersion());
data.setPackId(packet.getPackId()); data.setPackId(packet.getPackId());
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE; int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE;
long remainingSize = pack.getFile().length() - offset; long remainingSize = codec.size() - offset;
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)]; byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)];
try (InputStream inputStream = new FileInputStream(pack.getFile())) { try (SeekableByteChannel channel = codec.serialize(pack)) {
inputStream.skip(offset); channel.position(offset);
inputStream.read(packData, 0, packData.length); channel.read(ByteBuffer.wrap(packData, 0, packData.length));
} catch (Exception e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -314,7 +326,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.sendUpstreamPacket(data); session.sendUpstreamPacket(data);
// Check if it is the last chunk and send next pack in queue when available. // Check if it is the last chunk and send next pack in queue when available.
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) { if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
sendPackDataInfo(packsToSent.pop()); sendPackDataInfo(packsToSent.pop());
} }
@ -324,15 +336,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
private void sendPackDataInfo(String id) { private void sendPackDataInfo(String id) {
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
String[] packID = id.split("_"); String[] packID = id.split("_");
ResourcePack pack = ResourcePack.PACKS.get(packID[0]); ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
ResourcePackManifest.Header header = pack.getManifest().getHeader(); PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header();
data.setPackId(header.getUuid()); data.setPackId(header.uuid());
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE); int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
data.setChunkCount(chunkCount); data.setChunkCount(chunkCount);
data.setCompressedPackSize(pack.getFile().length()); data.setCompressedPackSize(codec.size());
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); data.setMaxChunkSize(GeyserResourcePack.CHUNK_SIZE);
data.setHash(pack.getSha256()); data.setHash(codec.sha256());
data.setPackVersion(packID[1]); data.setPackVersion(packID[1]);
data.setPremium(false); data.setPremium(false);
data.setType(ResourcePackType.RESOURCES); data.setType(ResourcePackType.RESOURCES);

View file

@ -0,0 +1,38 @@
/*
* 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.pack;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack {
/**
* The size of each chunk to use when sending the resource packs to clients in bytes
*/
public static final int CHUNK_SIZE = 102400;
}

View file

@ -0,0 +1,65 @@
/*
* 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.pack;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.UUID;
public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection<Module> modules, Collection<Dependency> dependencies) implements ResourcePackManifest {
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
@JsonDeserialize(using = Version.VersionDeserializer.class)
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
@Override
public @NotNull String toString() {
return major + "." + minor + "." + patch;
}
public static class VersionDeserializer extends JsonDeserializer<Version> {
@Override
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
int[] version = ctxt.readValue(p, int[].class);
return new Version(version[0], version[1], version[2]);
}
}
}
}

View file

@ -1,160 +0,0 @@
/*
* Copyright (c) 2019-2022 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.pack;
import lombok.Getter;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* This represents a resource pack and all the data relevant to it
*/
public class ResourcePack {
/**
* The list of loaded resource packs
*/
public static final Map<String, ResourcePack> PACKS = new HashMap<>();
/**
* The size of each chunk to use when sending the resource packs to clients in bytes
*/
public static final int CHUNK_SIZE = 102400;
private byte[] sha256;
private File file;
private ResourcePackManifest manifest;
private ResourcePackManifest.Version version;
@Getter
private String contentKey;
/**
* Loop through the packs directory and locate valid resource pack files
*/
public static void loadPacks() {
Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs");
if (!Files.exists(directory)) {
try {
Files.createDirectory(directory);
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
}
// As we just created the directory it will be empty
return;
}
List<Path> resourcePacks;
try {
resourcePacks = Files.walk(directory).collect(Collectors.toList());
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
return;
}
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
GeyserImpl.getInstance().eventBus().fire(event);
for (Path path : event.resourcePacks()) {
File file = path.toFile();
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
ResourcePack pack = new ResourcePack();
pack.sha256 = FileUtils.calculateSHA256(file);
try (ZipFile zip = new ZipFile(file);
Stream<? extends ZipEntry> stream = zip.stream()) {
stream.forEach((x) -> {
String name = x.getName();
if (name.length() >= 80) {
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
+ " 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 {
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
// Sometimes a pack_manifest file is present and not in a valid format,
// but a manifest file is, so we null check through that one
if (manifest.getHeader().getUuid() != null) {
pack.file = file;
pack.manifest = manifest;
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 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)
File keyFile = new File(file.getParentFile(), file.getName() + ".key");
pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : "";
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
e.printStackTrace();
}
}
}
}
public byte[] getSha256() {
return sha256;
}
public File getFile() {
return file;
}
public ResourcePackManifest getManifest() {
return manifest;
}
public ResourcePackManifest.Version getVersion() {
return version;
}
}

View file

@ -1,117 +0,0 @@
/*
* Copyright (c) 2019-2022 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.pack;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.Value;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
/**
* author: NukkitX
* Nukkit Project
*/
@Getter
@EqualsAndHashCode
public class ResourcePackManifest {
@JsonProperty("format_version")
private Integer formatVersion;
private Header header;
private Collection<Module> modules;
protected Collection<Dependency> dependencies;
public Collection<Module> getModules() {
return Collections.unmodifiableCollection(modules);
}
@Getter
@ToString
public static class Header {
private String description;
private String name;
private UUID uuid;
private int[] version;
@JsonProperty("min_engine_version")
private int[] minimumSupportedMinecraftVersion;
public String getVersionString() {
return version[0] + "." + version[1] + "." + version[2];
}
}
@Getter
@ToString
public static class Module {
private String description;
private String name;
private UUID uuid;
private int[] version;
}
@Getter
@ToString
public static class Dependency {
private UUID uuid;
private int[] version;
}
@Value
public static class Version {
private final int major;
private final int minor;
private final int patch;
public static Version fromString(String ver) {
String[] split = ver.replace(']', ' ')
.replace('[', ' ')
.replaceAll(" ", "").split(",");
return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
}
public static Version fromArray(int[] ver) {
return new Version(ver[0], ver[1], ver[2]);
}
private Version(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
@Override
public String toString() {
return major + "." + minor + "." + patch;
}
}
}

View file

@ -0,0 +1,109 @@
/*
* 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.pack.path;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
import org.geysermc.geyser.util.FileUtils;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
@RequiredArgsConstructor
public class GeyserPathPackCodec extends PathPackCodec {
private final Path path;
private FileTime lastModified;
private byte[] sha256;
private long size = -1;
@Override
public @NonNull Path path() {
this.checkLastModified();
return this.path;
}
@Override
public byte @NonNull [] sha256() {
this.checkLastModified();
if (this.sha256 != null) {
return this.sha256;
}
return this.sha256 = FileUtils.calculateSHA256(this.path);
}
@Override
public long size() {
this.checkLastModified();
if (this.size != -1) {
return this.size;
}
try {
return this.size = Files.size(this.path);
} catch (IOException e) {
throw new RuntimeException("Could not get file size of path " + this.path, e);
}
}
@Override
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
return FileChannel.open(this.path);
}
@Override
protected @NonNull ResourcePack create() {
return ResourcePackLoader.readPack(this.path);
}
private void checkLastModified() {
try {
FileTime lastModified = Files.getLastModifiedTime(this.path);
if (this.lastModified == null) {
this.lastModified = lastModified;
return;
}
if (lastModified.toInstant().isAfter(this.lastModified.toInstant())) {
GeyserImpl.getInstance().getLogger().warning("Detected a change in the resource pack " + path + ". This is likely to cause undefined behavior for new clients joining. It is suggested you restart Geyser.");
this.lastModified = lastModified;
this.sha256 = null;
this.size = -1;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,180 @@
/*
* 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;
import org.geysermc.geyser.registry.loader.RegistryLoader;
import org.geysermc.geyser.registry.loader.RegistryLoaders;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A deferred registry is a registry that is not loaded until it is needed.
* This is useful for registries that are not needed until after other parts
* of the lifecycle have been completed.
* <p>
* This class is slightly different from other registries in that it acts as
* a wrapper around another registry. This is to allow for any kind of registry
* type to be deferred.
*
* @param <M> the value being held by the registry
*/
public final class DeferredRegistry<M> implements IRegistry<M> {
private final Registry<M> backingRegistry;
private final Supplier<M> loader;
private boolean loaded;
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.load(null);
}
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.get().load(null);
}
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.load(input);
}
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
this.loader = () -> deferredLoader.get().load(input);
}
@Override
public M get() {
if (!this.loaded) {
throw new IllegalStateException("Registry has not been loaded yet!");
}
return this.backingRegistry.get();
}
@Override
public void set(M mappings) {
if (!this.loaded) {
throw new IllegalStateException("Registry has not been loaded yet!");
}
this.backingRegistry.set(mappings);
}
@Override
public void register(Consumer<M> consumer) {
if (!this.loaded) {
throw new IllegalStateException("Registry has not been loaded yet!");
}
this.backingRegistry.register(consumer);
}
/**
* Loads the registry.
*/
public void load() {
if (this.loaded) {
throw new IllegalStateException("Registry has already been loaded!");
}
this.backingRegistry.set(this.loader.get());
this.loaded = true;
}
/**
* Creates a new deferred registry.
*
* @param registryLoader the registry loader
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
return new DeferredRegistry<>(registryLoader, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryLoader the registry loader
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
return new DeferredRegistry<>(registryLoader, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryInitializer the registry initializer
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
}
/**
* Creates a new deferred registry.
*
* @param registryInitializer the registry initializer
* @param deferredLoader the deferred loader
* @param <I> the input type
* @param <M> the registry type
* @return the new deferred registry
*/
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
}
/**
* A registry initializer.
*
* @param <M> the registry type
*/
interface RegistryInitializer<M> {
/**
* Initializes the registry.
*
* @param input the input
* @param registryLoader the registry loader
* @param <I> the input type
* @return the initialized registry
*/
<I> Registry<M> initialize(I input, RegistryLoader<I, M> registryLoader);
}
}

View file

@ -0,0 +1,60 @@
/*
* 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;
import java.util.function.Consumer;
/**
* Represents a registry.
*
* @param <M> the value being held by the registry
*/
interface IRegistry<M> {
/**
* Gets the underlying value held by this registry.
*
* @return the underlying value held by this registry.
*/
M get();
/**
* Sets the underlying value held by this registry.
* Clears any existing data associated with the previous
* value.
*
* @param mappings the underlying value held by this registry
*/
void set(M mappings);
/**
* Registers what is specified in the given
* {@link Consumer} into the underlying value.
*
* @param consumer the consumer
*/
void register(Consumer<M> consumer);
}

View file

@ -41,6 +41,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData; import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData; import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
@ -158,6 +160,11 @@ public final class Registries {
*/ */
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
/**
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
*/
public static final DeferredRegistry<Map<String, ResourcePack>> RESOURCE_PACKS = DeferredRegistry.create(GeyserImpl.getInstance().packDirectory(), SimpleMappedRegistry::create, RegistryLoaders.RESOURCE_PACKS);
/** /**
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}. * A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
*/ */

View file

@ -64,7 +64,7 @@ import java.util.function.Consumer;
* *
* @param <M> the value being held by the registry * @param <M> the value being held by the registry
*/ */
public abstract class Registry<M> { public abstract class Registry<M> implements IRegistry<M> {
protected M mappings; protected M mappings;
/** /**
@ -85,6 +85,7 @@ public abstract class Registry<M> {
* *
* @return the underlying value held by this registry. * @return the underlying value held by this registry.
*/ */
@Override
public M get() { public M get() {
return this.mappings; return this.mappings;
} }
@ -96,6 +97,7 @@ public abstract class Registry<M> {
* *
* @param mappings the underlying value held by this registry * @param mappings the underlying value held by this registry
*/ */
@Override
public void set(M mappings) { public void set(M mappings) {
this.mappings = mappings; this.mappings = mappings;
} }
@ -106,6 +108,7 @@ public abstract class Registry<M> {
* *
* @param consumer the consumer * @param consumer the consumer
*/ */
@Override
public void register(Consumer<M> consumer) { public void register(Consumer<M> consumer) {
consumer.accept(this.mappings); consumer.accept(this.mappings);
} }

View file

@ -31,13 +31,16 @@ import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemData;
import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserCustomItemOptions;
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
import org.geysermc.geyser.registry.provider.ProviderSupplier; import org.geysermc.geyser.registry.provider.ProviderSupplier;
import java.nio.file.Path;
import java.util.Map; import java.util.Map;
/** /**
@ -47,11 +50,15 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
@Override @Override
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) { public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
// misc
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
// items
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder()); providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder()); providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
return providers; return providers;
} }

View file

@ -36,7 +36,12 @@ public final class RegistryLoaders {
/** /**
* The {@link RegistryLoader} responsible for loading NBT. * The {@link RegistryLoader} responsible for loading NBT.
*/ */
public static NbtRegistryLoader NBT = new NbtRegistryLoader(); public static final NbtRegistryLoader NBT = new NbtRegistryLoader();
/**
* The {@link RegistryLoader} responsible for loading resource packs.
*/
public static final ResourcePackLoader RESOURCE_PACKS = new ResourcePackLoader();
/** /**
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does * Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
@ -51,10 +56,14 @@ public final class RegistryLoaders {
} }
/** /**
* Returns a {@link RegistryLoader} which has not taken
* in any input value.
*
* @param <I> the input
* @param <V> the value * @param <V> the value
* @return a RegistryLoader that is yet to contain a value. * @return a RegistryLoader that is yet to contain a value.
*/ */
public static <V> RegistryLoader<Object, V> uninitialized() { public static <I, V> RegistryLoader<I, V> uninitialized() {
return input -> null; return input -> null;
} }

View file

@ -0,0 +1,156 @@
/*
* 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);
}
}
}

View file

@ -30,10 +30,9 @@ import org.geysermc.geyser.util.AssetUtils;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -80,14 +79,14 @@ public final class ProvidedSkins {
.resolve(slim ? "slim" : "wide"); .resolve(slim ? "slim" : "wide");
String assetName = asset.substring(asset.lastIndexOf('/') + 1); String assetName = asset.substring(asset.lastIndexOf('/') + 1);
File location = folder.resolve(assetName).toFile(); Path location = folder.resolve(assetName);
AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset, AssetUtils.addTask(!Files.exists(location), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
(stream) -> AssetUtils.saveFile(location, stream), (stream) -> AssetUtils.saveFile(location, stream),
() -> { () -> {
try { try {
// TODO lazy initialize? // TODO lazy initialize?
BufferedImage image; BufferedImage image;
try (InputStream stream = new FileInputStream(location)) { try (InputStream stream = Files.newInputStream(location)) {
image = ImageIO.read(stream); image = ImageIO.read(stream);
} }

View file

@ -34,6 +34,7 @@ import org.geysermc.geyser.util.WebUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
@ -57,8 +58,8 @@ public class MinecraftLocale {
} }
public static void ensureEN_US() { public static void ensureEN_US() {
File localeFile = getFile("en_us"); Path localeFile = getPath("en_us");
AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json", AssetUtils.addTask(!Files.exists(localeFile), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
(stream) -> AssetUtils.saveFile(localeFile, stream), (stream) -> AssetUtils.saveFile(localeFile, stream),
() -> { () -> {
if ("en_us".equals(GeyserLocale.getDefaultLocale())) { if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
@ -106,10 +107,10 @@ public class MinecraftLocale {
if (locale.equals("en_us")) { if (locale.equals("en_us")) {
return; return;
} }
File localeFile = getFile(locale); Path localeFile = getPath(locale);
// Check if we have already downloaded the locale file // Check if we have already downloaded the locale file
if (localeFile.exists()) { if (Files.exists(localeFile)) {
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash(); String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
@ -130,8 +131,8 @@ public class MinecraftLocale {
} }
} }
private static File getFile(String locale) { private static Path getPath(String locale) {
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json");
} }
/** /**

View file

@ -192,8 +192,8 @@ public final class AssetUtils {
} }
} }
public static void saveFile(File location, InputStream fileStream) throws IOException { public static void saveFile(Path location, InputStream fileStream) throws IOException {
try (FileOutputStream outStream = new FileOutputStream(location)) { try (OutputStream outStream = Files.newOutputStream(location)) {
// Write the file to the locale dir // Write the file to the locale dir
byte[] buf = new byte[fileStream.available()]; byte[] buf = new byte[fileStream.available()];

View file

@ -129,14 +129,14 @@ public class FileUtils {
/** /**
* Calculate the SHA256 hash of a file * Calculate the SHA256 hash of a file
* *
* @param file File to calculate the hash for * @param path Path to calculate the hash for
* @return A byte[] representation of the hash * @return A byte[] representation of the hash
*/ */
public static byte[] calculateSHA256(File file) { public static byte[] calculateSHA256(Path path) {
byte[] sha256; byte[] sha256;
try { try {
sha256 = MessageDigest.getInstance("SHA-256").digest(readAllBytes(file)); sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(path));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Could not calculate pack hash", e); throw new RuntimeException("Could not calculate pack hash", e);
} }
@ -147,14 +147,14 @@ public class FileUtils {
/** /**
* Calculate the SHA1 hash of a file * Calculate the SHA1 hash of a file
* *
* @param file File to calculate the hash for * @param path Path to calculate the hash for
* @return A byte[] representation of the hash * @return A byte[] representation of the hash
*/ */
public static byte[] calculateSHA1(File file) { public static byte[] calculateSHA1(Path path) {
byte[] sha1; byte[] sha1;
try { try {
sha1 = MessageDigest.getInstance("SHA-1").digest(readAllBytes(file)); sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Could not calculate pack hash", e); throw new RuntimeException("Could not calculate pack hash", e);
} }
@ -162,20 +162,6 @@ public class FileUtils {
return sha1; return sha1;
} }
/**
* An android compatible version of {@link Files#readAllBytes}
*
* @param file File to read bytes of
* @return The byte array of the file
*/
public static byte[] readAllBytes(File file) {
try (InputStream stream = new FileInputStream(file)) {
return stream.readAllBytes();
} catch (IOException e) {
throw new RuntimeException("Cannot read " + file);
}
}
/** /**
* @param resource the internal resource to read off from * @param resource the internal resource to read off from
* @return the byte array of an InputStream * @return the byte array of an InputStream

View file

@ -0,0 +1,57 @@
/*
* 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.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ResourcePackLoaderTest {
@Test
public void testPathMatcher() {
PathMatcher matcher = ResourcePackLoader.PACK_MATCHER;
assertTrue(matcher.matches(Path.of("pack.mcpack")));
assertTrue(matcher.matches(Path.of("pack.zip")));
assertTrue(matcher.matches(Path.of("packs", "pack.mcpack")));
assertTrue(matcher.matches(Path.of("packs", "category", "pack.mcpack")));
assertTrue(matcher.matches(Path.of("packs", "Resource+Pack+1.2.3.mcpack")));
assertTrue(matcher.matches(Path.of("Resource+Pack+1.2.3.mcpack")));
assertTrue(matcher.matches(Path.of("packs", "Resource+Pack+1.2.3.zip")));
assertTrue(matcher.matches(Path.of("Resource+Pack+1.2.3.zip")));
assertFalse(matcher.matches(Path.of("resource.pack")));
assertFalse(matcher.matches(Path.of("pack.7zip")));
assertFalse(matcher.matches(Path.of("packs")));
}
}