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 2fa9582d7..43251d74b 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 @@ -75,8 +75,8 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent { /** * Unregisters a {@link ResourcePack} or {@link ResourcePackCDNEntry} 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. + * @param uuid the UUID of the resource pack/CDN entry to remove. + * @return true whether the resource pack/CDN entry was removed successfully. */ public abstract boolean unregister(@NonNull UUID uuid); } 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 new file mode 100644 index 000000000..dc673338f --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineResourcePacksEvent.java @@ -0,0 +1,79 @@ +/* + * 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.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. + * + */ +public abstract class GeyserDefineResourcePacksEvent implements Event { + + /** + * Gets an unmodifiable list of {@link ResourcePack}s that will be sent to clients. + * + * @return an unmodifiable list of resource packs that will be sent to clients. + */ + 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. + * + * @param resourcePack a resource pack that will be sent to clients. + * @return true if the resource pack was added successfully, + * or false if already present + */ + public abstract boolean register(@NonNull ResourcePack resourcePack); + + /** + * Registers a {@link ResourcePackCDNEntry} to be sent to clients. + * + * @param entry a resource pack CDN entry that will be sent to clients. + */ + public abstract boolean register(@NonNull ResourcePackCDNEntry entry); + + /** + * Unregisters a {@link ResourcePack} or {@link ResourcePackCDNEntry} 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. + */ + public abstract boolean unregister(@NonNull UUID uuid); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java index e9b283ecb..d75e0de08 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserLoadResourcePacksEvent.java @@ -33,8 +33,11 @@ import java.util.List; /** * Called when resource packs are loaded within Geyser. + * @deprecated Use {@link GeyserDefineResourcePacksEvent} instead. * * @param resourcePacks a mutable list of the currently listed resource packs */ -public record GeyserLoadResourcePacksEvent(@NonNull List resourcePacks) implements Event { + +@Deprecated(forRemoval = true) +public record GeyserLoadResourcePacksEvent(@Deprecated @NonNull List resourcePacks) implements Event { } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 1b3b89595..c16cc494c 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -30,7 +30,6 @@ import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; -import org.geysermc.geyser.api.pack.ResourcePackCDNEntry; import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.GeyserLocale; @@ -97,7 +96,7 @@ public interface GeyserConfiguration { boolean isForceResourcePacks(); - List getCDNResourcePacks(); + List getCdnResourcePacks(); boolean isXboxAchievementsEnabled(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index bee041196..8211645ef 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -36,14 +36,16 @@ import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.network.AuthType; -import org.geysermc.geyser.api.pack.ResourcePackCDNEntry; import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; @Getter @@ -136,7 +138,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private boolean forceResourcePacks = true; @JsonProperty("cdn-resource-packs") - private Map cdnResourcePacks = new HashMap<>(); + private List cdnResourcePacks = new ArrayList<>(); @JsonProperty("xbox-achievements-enabled") private boolean xboxAchievementsEnabled = false; @@ -345,13 +347,4 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration return AuthType.getByName(p.getValueAsString()); } } - - @Override - public List getCDNResourcePacks() { - List entries = new ArrayList<>(); - for (Map.Entry entry : cdnResourcePacks.entrySet()) { - entries.add(new ResourcePackCDNEntry(entry.getValue(), entry.getKey())); - } - return entries; - } } 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 new file mode 100644 index 000000000..cf7f024fe --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineResourcePacksEventImpl.java @@ -0,0 +1,93 @@ +/* + * 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.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 { + + private final Map packs; + private final Map cdnEntries; + + public GeyserDefineResourcePacksEventImpl(Map packMap, List cdnEntries) { + 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)) { + 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; + } + } +} 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 b82f7b98d..974e704fd 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 @@ -31,6 +31,7 @@ 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; @@ -38,13 +39,13 @@ import java.util.UUID; public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent { private final Map packs; - - private final List cdnEntries; + private final Map cdnEntries; public SessionLoadResourcePacksEventImpl(GeyserSession session, Map packMap, List cdnEntries) { super(session); this.packs = packMap; - this.cdnEntries = cdnEntries; + this.cdnEntries = new HashMap<>(); + cdnEntries.forEach(entry -> this.cdnEntries.put(entry.uuid().toString(), entry)); } public @NonNull Map getPacks() { @@ -58,14 +59,13 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE @Override public @NonNull List cdnEntries() { - return List.copyOf(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.isEmpty() && cdnEntries.stream().anyMatch(entry -> entry.uuid().toString().equals(packID))) { + if (packs.containsValue(resourcePack) || packs.containsKey(packID) || cdnEntries.containsKey(packID)) { return false; } packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack); @@ -74,12 +74,11 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE @Override public boolean register(@NonNull ResourcePackCDNEntry entry) { - UUID packID = entry.uuid(); - if (packs.containsKey(packID.toString()) || cdnEntries.contains(entry) - || !cdnEntries.isEmpty() && cdnEntries.stream().anyMatch(cdnEntry -> cdnEntry.uuid().equals(packID))) { + String packID = entry.uuid().toString(); + if (packs.containsKey(packID) || cdnEntries.containsKey(packID) || cdnEntries.containsValue(entry)) { return false; } - cdnEntries.add(entry); + cdnEntries.put(packID, entry); return true; } @@ -87,8 +86,8 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE public boolean unregister(@NonNull UUID uuid) { if (packs.containsKey(uuid.toString())) { return packs.remove(uuid.toString()) != null; - } else if (!cdnEntries.isEmpty() && cdnEntries.stream().anyMatch(entry -> entry.uuid().equals(uuid))) { - return cdnEntries.removeIf(entry -> entry.uuid().equals(uuid)); + } else if (cdnEntries.containsKey(uuid.toString())) { + return cdnEntries.remove(uuid.toString()) != null; } else { return false; } 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 f11f938b4..f4afc551a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -57,6 +57,7 @@ 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; @@ -179,8 +180,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { geyser.getSessionManager().addPendingSession(session); - GeyserImpl.getInstance().getLogger().error(geyser.getConfig().getCDNResourcePacks().toString()); - this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()), geyser.getConfig().getCDNResourcePacks()); + this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()), ResourcePackLoader.RESOURCE_PACK_CDN_ENTRY_LIST); this.geyser.eventBus().fire(this.resourcePackLoadEvent); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); 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 800a3d22c..f9b962a64 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 @@ -28,12 +28,15 @@ 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.api.pack.ResourcePackCDNEntry; +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.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.WebUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -45,6 +48,7 @@ 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; @@ -58,8 +62,12 @@ public class ResourcePackLoader implements RegistryLoader RESOURCE_PACK_CDN_ENTRY_LIST = new ArrayList<>(); + /** * Loop through the packs directory and locate valid resource pack files */ @@ -94,6 +102,33 @@ public class ResourcePackLoader implements RegistryLoader cdnPacks = GeyserImpl.getInstance().getConfig().getCdnResourcePacks(); + for (String url: cdnPacks) { + int packHash = url.hashCode(); + Path cachedPath = CACHED_CDN_PACKS_DIRECTORY.resolve(packHash + ".zip"); + WebUtils.downloadFile(url, cachedPath.toString()); + + ResourcePack cdnpack = readPack(cachedPath); + UUID uuid = cdnpack.manifest().header().uuid(); + + RESOURCE_PACK_CDN_ENTRY_LIST.add(new ResourcePackCDNEntry(url, uuid)); + + try { + Files.delete(cachedPath); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error("Could not delete cached pack", e); + } + } + GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks); GeyserImpl.getInstance().eventBus().fire(event); @@ -105,6 +140,11 @@ public class ResourcePackLoader implements RegistryLoader