From b9c5bddb975a04f5655ad7d56b12cbf444adf072 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Tue, 17 Oct 2023 19:06:11 +0200 Subject: [PATCH] Remove CDNEntry; those do not work as expected. Instead: - add UrlPackCodec & GeyserUrlPackCodec - try and provide the resource pack via a stream from the url - either because the client does not support packs via the url, or because it failed to get the packs --- .../SessionLoadResourcePacksEvent.java | 17 +-- .../GeyserDefineResourcePacksEvent.java | 19 +-- .../geysermc/geyser/api/pack/PackCodec.java | 12 ++ ...rcePackCDNEntry.java => UrlPackCodec.java} | 29 +++-- .../GeyserDefineResourcePacksEventImpl.java | 38 +----- .../SessionLoadResourcePacksEventImpl.java | 39 +----- .../geyser/network/UpstreamPacketHandler.java | 61 ++++------ .../geyser/pack/GeyserResourcePack.java | 3 +- .../geyser/pack/url/GeyserUrlPackCodec.java | 112 ++++++++++++++++++ .../loader/ProviderRegistryLoader.java | 3 + .../registry/loader/ResourcePackLoader.java | 60 +++++----- 11 files changed, 214 insertions(+), 179 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/pack/{ResourcePackCDNEntry.java => UrlPackCodec.java} (73%) create mode 100644 core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java index 8b05270d5..26909f046 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java @@ -29,7 +29,6 @@ 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 org.geysermc.geyser.api.pack.ResourcePackCDNEntry; import java.util.List; import java.util.UUID; @@ -49,13 +48,6 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent { */ public abstract @NonNull List resourcePacks(); - /** - * Gets an unmodifiable list of {@link ResourcePackCDNEntry}s that will be sent to the client. - * - * @return an unmodifiable list of resource pack CDN entries that will be sent to the client. - */ - public abstract @NonNull List cdnEntries(); - /** * Registers a {@link ResourcePack} to be sent to the client. * @@ -66,14 +58,7 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent { public abstract boolean register(@NonNull ResourcePack resourcePack); /** - * Registers a {@link ResourcePackCDNEntry} to be sent to the client. - * - * @param entry CDN entry that will be sent to the client to download a resource pack from. - */ - public abstract boolean register(@NonNull ResourcePackCDNEntry entry); - - /** - * Unregisters a {@link ResourcePack} or {@link ResourcePackCDNEntry} from being sent to the client. + * Unregisters a {@link ResourcePack} from being sent to the client. * * @param uuid the UUID of the resource pack/CDN entry to remove. * @return true whether the resource pack/CDN entry was removed successfully. diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineResourcePacksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineResourcePacksEvent.java index 62f02e8d2..6da2b1f4f 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineResourcePacksEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineResourcePacksEvent.java @@ -28,13 +28,12 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.Event; import org.geysermc.geyser.api.pack.ResourcePack; -import org.geysermc.geyser.api.pack.ResourcePackCDNEntry; import java.util.List; import java.util.UUID; /** - * Called when {@link ResourcePack}'s and {@link ResourcePackCDNEntry}'s are loaded within Geyser. + * Called when {@link ResourcePack}'s are loaded within Geyser. */ public abstract class GeyserDefineResourcePacksEvent implements Event { @@ -45,13 +44,6 @@ public abstract class GeyserDefineResourcePacksEvent implements Event { */ public abstract @NonNull List resourcePacks(); - /** - * Gets an unmodifiable list of {@link ResourcePackCDNEntry}s that will be sent to clients. - * - * @return an unmodifiable list of resource pack CDN entries that will be sent to clients. - */ - public abstract @NonNull List cdnEntries(); - /** * Registers a {@link ResourcePack} to be sent to clients. * @@ -62,14 +54,7 @@ public abstract class GeyserDefineResourcePacksEvent implements Event { public abstract boolean register(@NonNull ResourcePack resourcePack); /** - * Registers a {@link ResourcePackCDNEntry} to be sent to clients. - * - * @param entry CDN entry that will be sent to the client to download a resource pack from. - */ - public abstract boolean register(@NonNull ResourcePackCDNEntry entry); - - /** - * Unregisters a {@link ResourcePack} or {@link ResourcePackCDNEntry} from being sent to clients. + * Unregisters a {@link ResourcePack} from being sent to clients. * * @param uuid the UUID of the resource pack/CDN entry to remove. * @return true whether the resource pack/CDN entry was removed successfully. diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java index 884129fa3..ff9f82c5a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java @@ -79,4 +79,16 @@ public abstract class PackCodec { public static PackCodec path(@NonNull Path path) { return GeyserApi.api().provider(PathPackCodec.class, path); } + + /** + * Creates a new pack provider from the given url and content key. + * + * @param url the url to create the pack provider from + * @param contentKey the content key, leave empty if pack is not encrypted + * @return the new pack provider + */ + @NonNull + public static PackCodec url(@NonNull String url, @NonNull String contentKey) { + return GeyserApi.api().provider(UrlPackCodec.class, url, contentKey); + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackCDNEntry.java b/api/src/main/java/org/geysermc/geyser/api/pack/UrlPackCodec.java similarity index 73% rename from api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackCDNEntry.java rename to api/src/main/java/org/geysermc/geyser/api/pack/UrlPackCodec.java index 620147f85..e6d1a5a24 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackCDNEntry.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/UrlPackCodec.java @@ -25,16 +25,23 @@ package org.geysermc.geyser.api.pack; -import java.util.UUID; +import org.checkerframework.checker.nullness.qual.NonNull; -/** - * Represents a CDN entry for a resource pack. - * The URL must be a direct download link to a Bedrock edition resource pack. - * The UUID must be the UUID of the resource pack. - * - * @param url URL from which the pack should be downloaded - * @param uuid UUID of the pack - */ -public record ResourcePackCDNEntry(String url, UUID uuid) { +public abstract class UrlPackCodec extends PackCodec { + + /** + * Gets the URL of the resource pack. + * + * @return the URL of the resource pack + */ + @NonNull + public abstract String url(); + + /** + * If the remote pack has an encryption key, it must be specified here. + * Otherwise, leave empty. + * + */ + @NonNull + public abstract String contentKey(); } - diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java index cf7f024fe..2fae61b54 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java @@ -25,29 +25,22 @@ package org.geysermc.geyser.event.type; +import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineResourcePacksEvent; import org.geysermc.geyser.api.pack.ResourcePack; -import org.geysermc.geyser.api.pack.ResourcePackCDNEntry; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent { + @Getter private final Map packs; - private final Map cdnEntries; - public GeyserDefineResourcePacksEventImpl(Map packMap, List cdnEntries) { + public GeyserDefineResourcePacksEventImpl(Map packMap) { this.packs = packMap; - this.cdnEntries = new HashMap<>(); - cdnEntries.forEach(entry -> this.cdnEntries.put(entry.uuid().toString(), entry)); - } - - public @NonNull Map getPacks() { - return packs; } @Override @@ -55,39 +48,18 @@ public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePack return List.copyOf(packs.values()); } - @Override - public @NonNull List cdnEntries() { - return List.copyOf(cdnEntries.values()); - } - @Override public boolean register(@NonNull ResourcePack resourcePack) { String packID = resourcePack.manifest().header().uuid().toString(); - if (packs.containsValue(resourcePack) || packs.containsKey(packID) || cdnEntries.containsKey(packID)) { + if (packs.containsValue(resourcePack) || packs.containsKey(packID)) { return false; } packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack); return true; } - @Override - public boolean register(@NonNull ResourcePackCDNEntry entry) { - String packID = entry.uuid().toString(); - if (packs.containsKey(packID) || cdnEntries.containsValue(entry) || cdnEntries.containsKey(packID)) { - return false; - } - cdnEntries.put(packID, entry); - return true; - } - @Override public boolean unregister(@NonNull UUID uuid) { - if (packs.containsKey(uuid.toString())) { - return packs.remove(uuid.toString()) != null; - } else if (cdnEntries.containsKey(uuid.toString())) { - return cdnEntries.remove(uuid.toString()) != null; - } else { - return false; - } + return packs.remove(uuid.toString()) != null; } } diff --git a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java index 974e704fd..e66396d6a 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java @@ -25,71 +25,42 @@ package org.geysermc.geyser.event.type; +import lombok.Getter; 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.api.pack.ResourcePackCDNEntry; import org.geysermc.geyser.session.GeyserSession; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent { + @Getter private final Map packs; - private final Map cdnEntries; - public SessionLoadResourcePacksEventImpl(GeyserSession session, Map packMap, List cdnEntries) { + public SessionLoadResourcePacksEventImpl(GeyserSession session, Map packMap) { super(session); this.packs = packMap; - this.cdnEntries = new HashMap<>(); - cdnEntries.forEach(entry -> this.cdnEntries.put(entry.uuid().toString(), entry)); } - - public @NonNull Map getPacks() { - return packs; - } - @Override public @NonNull List resourcePacks() { return List.copyOf(packs.values()); } - @Override - public @NonNull List cdnEntries() { - return List.copyOf(cdnEntries.values()); - } - @Override public boolean register(@NonNull ResourcePack resourcePack) { String packID = resourcePack.manifest().header().uuid().toString(); - if (packs.containsValue(resourcePack) || packs.containsKey(packID) || cdnEntries.containsKey(packID)) { + if (packs.containsValue(resourcePack) || packs.containsKey(packID)) { return false; } packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack); return true; } - @Override - public boolean register(@NonNull ResourcePackCDNEntry entry) { - String packID = entry.uuid().toString(); - if (packs.containsKey(packID) || cdnEntries.containsKey(packID) || cdnEntries.containsValue(entry)) { - return false; - } - cdnEntries.put(packID, entry); - return true; - } - @Override public boolean unregister(@NonNull UUID uuid) { - if (packs.containsKey(uuid.toString())) { - return packs.remove(uuid.toString()) != null; - } else if (cdnEntries.containsKey(uuid.toString())) { - return cdnEntries.remove(uuid.toString()) != null; - } else { - return false; - } + return packs.remove(uuid.toString()) != null; } } diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index 1ef6170a1..863a728a0 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -31,33 +31,19 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.data.ExperimentData; import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm; import org.cloudburstmc.protocol.bedrock.data.ResourcePackType; -import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; -import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; -import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket; -import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; -import org.cloudburstmc.protocol.bedrock.packet.NetworkSettingsPacket; -import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; -import org.cloudburstmc.protocol.bedrock.packet.RequestNetworkSettingsPacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkDataPacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkRequestPacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePackClientResponsePacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePackDataInfoPacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePackStackPacket; -import org.cloudburstmc.protocol.bedrock.packet.ResourcePacksInfoPacket; -import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; +import org.cloudburstmc.protocol.bedrock.packet.*; import org.cloudburstmc.protocol.common.PacketSignal; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; 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.ResourcePackCDNEntry; import org.geysermc.geyser.api.pack.ResourcePackManifest; +import org.geysermc.geyser.api.pack.UrlPackCodec; import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl; import org.geysermc.geyser.pack.GeyserResourcePack; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.loader.ResourcePackLoader; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.text.GeyserLocale; @@ -180,26 +166,26 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { geyser.getSessionManager().addPendingSession(session); - this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()), ResourcePackLoader.RESOURCE_PACK_CDN_ENTRIES); + this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get())); this.geyser.eventBus().fire(this.resourcePackLoadEvent); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) { PackCodec codec = pack.codec(); ResourcePackManifest.Header header = pack.manifest().header(); + + if (pack.codec() instanceof UrlPackCodec urlPackCodec) { + resourcePacksInfo.getCDNEntries().add(new ResourcePacksInfoPacket.CDNEntry( + header.uuid() + "_" + header.version(), urlPackCodec.url())); + } + resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(), "", header.uuid().toString(), false, false)); } - // Add CDN entries if the client supports it - if (!GameProtocol.isPre1_20_30(session)) { - for (ResourcePackCDNEntry entry : this.resourcePackLoadEvent.cdnEntries()) { - resourcePacksInfo.getCDNEntries().add(new ResourcePacksInfoPacket.CDNEntry(entry.uuid().toString(), entry.url())); - } - } - resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); + GeyserImpl.getInstance().getLogger().info(resourcePacksInfo.toString()); session.sendUpstreamPacket(resourcePacksInfo); GeyserLocale.loadGeyserLocale(session.locale()); @@ -209,7 +195,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { - case COMPLETED: + case COMPLETED -> { if (geyser.getConfig().getRemote().authType() != AuthType.ONLINE) { session.authenticate(session.getAuthData().name()); } else if (!couldLoginUserByName(session.getAuthData().name())) { @@ -217,35 +203,27 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.connect(); } geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.connect", session.getAuthData().name())); - break; - - case SEND_PACKS: + } + case SEND_PACKS -> { packsToSent.addAll(packet.getPackIds()); sendPackDataInfo(packsToSent.pop()); - break; - - case HAVE_ALL_PACKS: + } + case HAVE_ALL_PACKS -> { ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); stackPacket.setExperimentsPreviouslyToggled(false); stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not stackPacket.setGameVersion(session.getClientData().getGameVersion()); - for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) { ResourcePackManifest.Header header = pack.manifest().header(); stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), "")); } - if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) { // Allow custom items to work stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); } - session.sendUpstreamPacket(stackPacket); - break; - - default: - session.disconnect("disconnectionScreen.resourcePack"); - break; + } + default -> session.disconnect("disconnectionScreen.resourcePack"); } return PacketSignal.HANDLED; @@ -297,6 +275,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString()); PackCodec codec = pack.codec(); + if (codec instanceof UrlPackCodec urlPackCodec) { + GeyserImpl.getInstance().getLogger().warning("Received ResourcePackChunkRequestPacket for URL pack " + urlPackCodec.url()); + } + data.setChunkIndex(packet.getChunkIndex()); data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE); data.setPackVersion(packet.getPackVersion()); @@ -331,7 +313,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]); PackCodec codec = pack.codec(); ResourcePackManifest.Header header = pack.manifest().header(); - data.setPackId(header.uuid()); int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE); data.setChunkCount(chunkCount); diff --git a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java index 82408b6e7..b50696a55 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java @@ -25,11 +25,12 @@ package org.geysermc.geyser.pack; +import org.checkerframework.checker.nullness.qual.NonNull; 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 { +public record GeyserResourcePack(@NonNull PackCodec codec, @NonNull ResourcePackManifest manifest, @NonNull String contentKey) implements ResourcePack { /** * The size of each chunk to use when sending the resource packs to clients in bytes diff --git a/core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java b/core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java new file mode 100644 index 000000000..fc8665679 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java @@ -0,0 +1,112 @@ +/* + * 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.url; + +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.pack.ResourcePack; +import org.geysermc.geyser.api.pack.UrlPackCodec; +import org.geysermc.geyser.registry.loader.ResourcePackLoader; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.SeekableByteChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +@RequiredArgsConstructor +public class GeyserUrlPackCodec extends UrlPackCodec { + + private final String url; + private final String contentKey; + + public GeyserUrlPackCodec(String url) { + this.url = url; + this.contentKey = ""; + } + + @Override + public byte @NonNull [] sha256() { + try { + URL resourcePackURL = new URL(this.url); + InputStream inputStream = resourcePackURL.openStream(); + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead); + } + return digest.digest(); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Override + public long size() { + URLConnection conn = null; + try { + conn = new URL(this.url).openConnection(); + if(conn instanceof HttpURLConnection) { + ((HttpURLConnection)conn).setRequestMethod("HEAD"); + } + conn.getInputStream(); + return conn.getContentLength(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if(conn instanceof HttpURLConnection) { + ((HttpURLConnection)conn).disconnect(); + } + } + } + + @Override + public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException { + URL resourcePackURL = new URL(this.url); + URLConnection connection = resourcePackURL.openConnection(); + return (SeekableByteChannel) Channels.newChannel(connection.getInputStream()); + } + + @Override + protected @NonNull ResourcePack create() { + return ResourcePackLoader.downloadPack(this.url, this.contentKey); + } + + @Override + public @NonNull String url() { + return this.url; + } + + @Override + public @NonNull String contentKey() { + return this.contentKey; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 13d7a4d77..59960098f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -38,6 +38,7 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; +import org.geysermc.geyser.api.pack.UrlPackCodec; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.item.GeyserCustomItemData; @@ -50,6 +51,7 @@ import org.geysermc.geyser.level.block.GeyserJavaBlockState; import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData; import org.geysermc.geyser.pack.path.GeyserPathPackCodec; +import org.geysermc.geyser.pack.url.GeyserUrlPackCodec; import org.geysermc.geyser.registry.provider.ProviderSupplier; import java.nio.file.Path; @@ -74,6 +76,7 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0])); providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0])); + providers.put(UrlPackCodec.class, args -> new GeyserUrlPackCodec((String) args[0], (String) args[1])); // items providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java index 8a2f5abdf..a46525632 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java @@ -25,15 +25,17 @@ package org.geysermc.geyser.registry.loader; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent; import org.geysermc.geyser.api.pack.ResourcePack; -import org.geysermc.geyser.api.pack.ResourcePackCDNEntry; +import org.geysermc.geyser.api.pack.ResourcePackManifest; import org.geysermc.geyser.event.type.GeyserDefineResourcePacksEventImpl; import org.geysermc.geyser.pack.GeyserResourcePack; import org.geysermc.geyser.pack.GeyserResourcePackManifest; import org.geysermc.geyser.pack.SkullResourcePackManager; import org.geysermc.geyser.pack.path.GeyserPathPackCodec; +import org.geysermc.geyser.pack.url.GeyserUrlPackCodec; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.WebUtils; @@ -45,10 +47,8 @@ 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.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -64,14 +64,12 @@ public class ResourcePackLoader implements RegistryLoader RESOURCE_PACK_CDN_ENTRIES = new ArrayList<>(); - /** * Loop through the packs directory and locate valid resource pack files */ @Override public Map load(Path directory) { - Map packMap = new HashMap<>(); + Map packMap = new Object2ObjectOpenHashMap<>(); if (!Files.exists(directory)) { try { @@ -100,9 +98,6 @@ public class ResourcePackLoader implements RegistryLoader loadCdnEntries() { final Path cachedCdnPacksDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("cdn-packs"); // Download CDN packs to get the pack uuid's @@ -184,26 +181,35 @@ public class ResourcePackLoader implements RegistryLoader(); } } List cdnPacks = GeyserImpl.getInstance().getConfig().getResourcePackUrls(); + Map packMap = new Object2ObjectOpenHashMap<>(); + for (String url: cdnPacks) { - int packHash = url.hashCode(); - Path cachedPath = cachedCdnPacksDirectory.resolve(packHash + ".zip"); - WebUtils.downloadFile(url, cachedPath.toString()); - - ResourcePack cdnpack = readPack(cachedPath); - UUID uuid = cdnpack.manifest().header().uuid(); - - RESOURCE_PACK_CDN_ENTRIES.add(new ResourcePackCDNEntry(url, uuid)); - - try { - Files.delete(cachedPath); - } catch (IOException e) { - GeyserImpl.getInstance().getLogger().error("Could not delete cached pack", e); - } + GeyserImpl.getInstance().getLogger().info("Loading CDN pack " + url); + ResourcePack pack = downloadPack(url, ""); + packMap.put(pack.manifest().header().uuid().toString(), pack); } + return packMap; + } + + public static ResourcePack downloadPack(String url, String contentKey) throws IllegalArgumentException { + int packHash = url.hashCode(); + Path cachedPath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("cdn-packs").resolve(packHash + ".zip"); + WebUtils.downloadFile(url, cachedPath.toString()); + + ResourcePack temp = readPack(cachedPath); + ResourcePackManifest manifest = temp.manifest(); + + try { + Files.delete(cachedPath); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Could not delete cached pack", e); + } + + return new GeyserResourcePack(new GeyserUrlPackCodec(url, contentKey), manifest, contentKey); } }