Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 15:00:11 +01:00
Merge remote-tracking branch 'refs/remotes/origin/rp' into subpacks-rewrite-merge-urlpacks
# Conflicts: # api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoadResourcePacksEvent.java # core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java # core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java # core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java # core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java # core/src/main/java/org/geysermc/geyser/registry/loader/ResourcePackLoader.java
Dieser Commit ist enthalten in:
Commit
9279c70624
@ -45,7 +45,7 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an unmodifiable list of {@link ResourcePack}s that will be sent to the client.
|
||||
* 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.
|
||||
*/
|
||||
|
@ -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.event.lifecycle;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.event.Event;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Called when {@link ResourcePack}'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<ResourcePack> resourcePacks();
|
||||
|
||||
/**
|
||||
* 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 collection of {@link ResourcePack}'s to be sent to clients.
|
||||
*
|
||||
* @param resourcePacks a collection of resource pack's that will be sent to clients.
|
||||
*/
|
||||
public abstract void registerAll(@NonNull Collection<ResourcePack> resourcePacks);
|
||||
|
||||
|
||||
/**
|
||||
* Unregisters a {@link ResourcePack} from being sent to clients.
|
||||
*
|
||||
* @param uuid the UUID of the resource pack to remove.
|
||||
* @return true whether the resource pack was removed successfully.
|
||||
*/
|
||||
public abstract boolean unregister(@NonNull UUID uuid);
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public record GeyserLoadResourcePacksEvent(@NonNull List<Path> resourcePacks) implements Event {
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.api.pack;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -94,4 +95,27 @@ 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 with no content key.
|
||||
*
|
||||
* @param url the url to create the pack provider from
|
||||
* @return the new pack provider
|
||||
*/
|
||||
@NonNull
|
||||
public static PackCodec url(@NonNull String url) {
|
||||
return url(url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 or null if pack is not encrypted
|
||||
* @return the new pack provider
|
||||
*/
|
||||
@NonNull
|
||||
public static PackCodec url(@NonNull String url, @Nullable String contentKey) {
|
||||
return GeyserApi.api().provider(UrlPackCodec.class, url, contentKey);
|
||||
}
|
||||
}
|
||||
|
58
api/src/main/java/org/geysermc/geyser/api/pack/UrlPackCodec.java
Normale Datei
58
api/src/main/java/org/geysermc/geyser/api/pack/UrlPackCodec.java
Normale Datei
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 pack codec that creates a resource
|
||||
* pack from a URL.
|
||||
* <p>
|
||||
* Due to Bedrock limitations, the URL must:
|
||||
* <ul>
|
||||
* <li>be a direct download link to a .zip or .mcpack resource pack</li>
|
||||
* <li>use the application type `application/zip` and set a correct content length</li>
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class UrlPackCodec extends PackCodec {
|
||||
|
||||
/**
|
||||
* Gets the URL to the resource pack location.
|
||||
*
|
||||
* @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.
|
||||
* This will return empty if none is specified.
|
||||
*
|
||||
* @return the encryption key of the resource pack
|
||||
*/
|
||||
@NonNull
|
||||
public abstract String contentKey();
|
||||
}
|
@ -81,6 +81,7 @@ import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.network.netty.GeyserServer;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -719,7 +720,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
runIfNonNull(newsHandler, NewsHandler::shutdown);
|
||||
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
|
||||
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
ResourcePackLoader.clear();
|
||||
|
||||
this.setEnabled(false);
|
||||
}
|
||||
|
@ -96,6 +96,8 @@ public interface GeyserConfiguration {
|
||||
|
||||
boolean isForceResourcePacks();
|
||||
|
||||
List<String> getResourcePackUrls();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
boolean isXboxAchievementsEnabled();
|
||||
|
||||
|
@ -136,6 +136,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("force-resource-packs")
|
||||
private boolean forceResourcePacks = true;
|
||||
|
||||
@JsonProperty("resource-pack-urls")
|
||||
private List<String> resourcePackUrls = new ArrayList<>();
|
||||
|
||||
@JsonProperty("xbox-achievements-enabled")
|
||||
private boolean xboxAchievementsEnabled = false;
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 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 java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class GeyserDefineResourcePacksEventImpl extends GeyserDefineResourcePacksEvent {
|
||||
|
||||
private final Map<String, ResourcePack> packs;
|
||||
|
||||
public GeyserDefineResourcePacksEventImpl(Map<String, ResourcePack> packMap) {
|
||||
this.packs = packMap;
|
||||
}
|
||||
|
||||
@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 void registerAll(@NonNull Collection<ResourcePack> resourcePacks) {
|
||||
resourcePacks.forEach(this::register);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregister(@NonNull UUID uuid) {
|
||||
return packs.remove(uuid.toString()) != null;
|
||||
}
|
||||
}
|
@ -59,10 +59,12 @@ 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.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;
|
||||
@ -80,7 +82,7 @@ import java.util.OptionalInt;
|
||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
private boolean networkSettingsRequested = false;
|
||||
private final Deque<String> packsToSent = new ArrayDeque<>();
|
||||
private final Deque<String> packsToSend = new ArrayDeque<>();
|
||||
private final CompressionStrategy compressionStrategy;
|
||||
|
||||
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
|
||||
@ -204,6 +206,14 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries());
|
||||
|
||||
// TODO add url pack entries
|
||||
/*
|
||||
if (pack.codec() instanceof UrlPackCodec urlPackCodec) {
|
||||
resourcePacksInfo.getCDNEntries().add(new ResourcePacksInfoPacket.CDNEntry(
|
||||
header.uuid() + "_" + header.version(), urlPackCodec.url()));
|
||||
}
|
||||
*/
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||
session.sendUpstreamPacket(resourcePacksInfo);
|
||||
|
||||
@ -214,7 +224,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())) {
|
||||
@ -222,14 +232,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
session.connect();
|
||||
}
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.connect", session.getAuthData().name()));
|
||||
break;
|
||||
|
||||
case SEND_PACKS:
|
||||
packsToSent.addAll(packet.getPackIds());
|
||||
sendPackDataInfo(packsToSent.pop());
|
||||
break;
|
||||
|
||||
case HAVE_ALL_PACKS:
|
||||
}
|
||||
case SEND_PACKS -> {
|
||||
packsToSend.addAll(packet.getPackIds());
|
||||
sendPackDataInfo(packsToSend.pop());
|
||||
}
|
||||
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
|
||||
@ -245,11 +253,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
stackPacket.getExperiments().add(new ExperimentData("updateAnnouncedLive2023", true));
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
break;
|
||||
|
||||
default:
|
||||
session.disconnect("disconnectionScreen.resourcePack");
|
||||
break;
|
||||
}
|
||||
default -> session.disconnect("disconnectionScreen.resourcePack");
|
||||
}
|
||||
|
||||
return PacketSignal.HANDLED;
|
||||
@ -309,6 +314,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
PackCodec codec = pack.codec();
|
||||
|
||||
// If a remote pack ends up here, that usually implies that a client was not able to download the pack
|
||||
if (codec instanceof UrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.testRemotePack(session, urlPackCodec, packet.getPackId().toString(), packet.getPackVersion());
|
||||
}
|
||||
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setPackVersion(packet.getPackVersion());
|
||||
@ -330,8 +340,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
session.sendUpstreamPacket(data);
|
||||
|
||||
// Check if it is the last chunk and send next pack in queue when available.
|
||||
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||
sendPackDataInfo(packsToSent.pop());
|
||||
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSend.isEmpty()) {
|
||||
sendPackDataInfo(packsToSend.pop());
|
||||
}
|
||||
|
||||
return PacketSignal.HANDLED;
|
||||
|
@ -36,10 +36,10 @@ import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
public record GeyserResourcePack(
|
||||
PackCodec codec,
|
||||
ResourcePackManifest manifest,
|
||||
String contentKey,
|
||||
OptionHolder options
|
||||
@NonNull PackCodec codec,
|
||||
@NonNull ResourcePackManifest manifest,
|
||||
@NonNull String contentKey,
|
||||
@NonNull OptionHolder options
|
||||
) implements ResourcePack {
|
||||
|
||||
/**
|
||||
|
103
core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java
Normale Datei
103
core/src/main/java/org/geysermc/geyser/pack/url/GeyserUrlPackCodec.java
Normale Datei
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
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.nio.channels.SeekableByteChannel;
|
||||
import java.util.Objects;
|
||||
|
||||
public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
private final @NonNull String url;
|
||||
private final @Nullable String contentKey;
|
||||
@Getter
|
||||
private PathPackCodec fallback;
|
||||
|
||||
public GeyserUrlPackCodec(String url) throws IllegalArgumentException {
|
||||
this(url, null);
|
||||
}
|
||||
|
||||
public GeyserUrlPackCodec(@NonNull String url, @Nullable String contentKey) throws IllegalArgumentException {
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
this.url = url;
|
||||
this.contentKey = contentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the sha256!");
|
||||
return fallback.sha256();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the size!");
|
||||
return fallback.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to serialize!!");
|
||||
return fallback.serialize(resourcePack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ResourcePack create() {
|
||||
if (this.fallback == null) {
|
||||
try {
|
||||
ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
|
||||
if (throwable != null) {
|
||||
throw new IllegalArgumentException(throwable);
|
||||
} else if (pack != null) {
|
||||
this.fallback = pack;
|
||||
}
|
||||
}).join(); // Needed to ensure that we don't attempt to read a pack before downloading/checking it
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to download pack from the url %s (reason: %s)!".formatted(url, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return ResourcePackLoader.readPack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String url() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String contentKey() {
|
||||
return this.contentKey != null ? contentKey : "";
|
||||
}
|
||||
}
|
@ -40,6 +40,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.api.pack.option.PriorityOption;
|
||||
import org.geysermc.geyser.api.pack.option.SubpackOption;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
@ -58,6 +59,7 @@ import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.pack.option.GeyserPriorityOption;
|
||||
import org.geysermc.geyser.pack.option.GeyserSubpackOption;
|
||||
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;
|
||||
@ -88,6 +90,7 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
||||
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||
providers.put(PriorityOption.class, args -> new GeyserPriorityOption((double) args[0]));
|
||||
providers.put(SubpackOption.class, args -> new GeyserSubpackOption((String) args[0]));
|
||||
providers.put(UrlPackCodec.class, args -> new GeyserUrlPackCodec((String) args[0]));
|
||||
|
||||
// items
|
||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder());
|
||||
|
@ -25,27 +25,39 @@
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
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.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
import org.geysermc.geyser.util.WebUtils;
|
||||
|
||||
import java.io.File;
|
||||
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.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@ -53,10 +65,18 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
|
||||
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserDefineResourcePacksEventImpl}.
|
||||
*/
|
||||
public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, GeyserResourcePack>> {
|
||||
|
||||
/**
|
||||
* Used to keep track of remote resource packs that the client rejected.
|
||||
* If a client rejects such a pack, it falls back to the old method, and Geyser serves a cached variant.
|
||||
*/
|
||||
private static final Cache<String, UrlPackCodec> CACHED_FAILED_PACKS = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
|
||||
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"));
|
||||
@ -66,7 +86,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
*/
|
||||
@Override
|
||||
public Map<UUID, GeyserResourcePack> load(Path directory) {
|
||||
Map<UUID, GeyserResourcePack> packMap = new HashMap<>();
|
||||
Map<UUID, GeyserResourcePack> packMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
if (!Files.exists(directory)) {
|
||||
try {
|
||||
@ -95,6 +115,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
resourcePacks.add(skullResourcePack);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
@ -106,7 +127,11 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return packMap;
|
||||
|
||||
packMap.putAll(loadRemotePacks());
|
||||
GeyserDefineResourcePacksEventImpl defineEvent = new GeyserDefineResourcePacksEventImpl(packMap);
|
||||
GeyserImpl.getInstance().eventBus().fire(defineEvent);
|
||||
return defineEvent.getPacks();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,10 +143,41 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
|
||||
*/
|
||||
public static GeyserResourcePack.Builder readPack(Path path) throws IllegalArgumentException {
|
||||
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
|
||||
if (!PACK_MATCHER.matches(path)) {
|
||||
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
|
||||
}
|
||||
|
||||
ResourcePackManifest manifest = readManifest(path, path.getFileName().toString());
|
||||
String contentKey;
|
||||
|
||||
try {
|
||||
// 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");
|
||||
contentKey = Files.exists(keyFile) ? Files.readString(keyFile, StandardCharsets.UTF_8) : "";
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read content key for resource pack " + path.getFileName(), e);
|
||||
contentKey = "";
|
||||
}
|
||||
|
||||
return new GeyserResourcePack.Builder(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a Resource pack from a URL codec, and returns a resource pack. Unlike {@link ResourcePackLoader#readPack(Path)}
|
||||
* this method reads content keys differently.
|
||||
*
|
||||
* @param codec the URL pack codec with the url to download the pack from
|
||||
* @return a {@link GeyserResourcePack} representation
|
||||
* @throws IllegalArgumentException if there was an error reading the pack.
|
||||
*/
|
||||
public static GeyserResourcePack readPack(GeyserUrlPackCodec codec) throws IllegalArgumentException {
|
||||
Path path = codec.getFallback().path();
|
||||
ResourcePackManifest manifest = readManifest(path, codec.url());
|
||||
return new GeyserResourcePack(codec, manifest, codec.contentKey());
|
||||
}
|
||||
|
||||
private static ResourcePackManifest readManifest(Path path, String packLocation) throws IllegalArgumentException {
|
||||
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
|
||||
|
||||
try (ZipFile zip = new ZipFile(path.toFile());
|
||||
@ -129,7 +185,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
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()
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + packLocation
|
||||
+ " 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.");
|
||||
@ -148,17 +204,192 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Geyser
|
||||
|
||||
GeyserResourcePackManifest manifest = manifestReference.get();
|
||||
if (manifest == null) {
|
||||
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
|
||||
throw new IllegalArgumentException(packLocation + " 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(keyFile, StandardCharsets.UTF_8) : "";
|
||||
|
||||
return new GeyserResourcePack.Builder(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||
return manifest;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", packLocation), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, ResourcePack> loadRemotePacks() {
|
||||
GeyserImpl instance = GeyserImpl.getInstance();
|
||||
// Unable to make this a static variable, as the test would fail
|
||||
final Path cachedCdnPacksDirectory = instance.getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
|
||||
|
||||
if (!Files.exists(cachedCdnPacksDirectory)) {
|
||||
try {
|
||||
Files.createDirectories(cachedCdnPacksDirectory);
|
||||
} catch (IOException e) {
|
||||
instance.getLogger().error("Could not create remote pack cache directory", e);
|
||||
return new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
List<String> remotePackUrls = instance.getConfig().getResourcePackUrls();
|
||||
Map<String, ResourcePack> packMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
for (String url : remotePackUrls) {
|
||||
try {
|
||||
GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url);
|
||||
ResourcePack pack = codec.create();
|
||||
packMap.put(pack.manifest().header().uuid().toString(), pack);
|
||||
} catch (Throwable e) {
|
||||
instance.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url));
|
||||
instance.getLogger().error(e.getMessage());
|
||||
if (instance.getLogger().isDebug()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After loading the new resource packs: let's clean up the old
|
||||
cleanupRemotePacks();
|
||||
|
||||
return packMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a Bedrock client requests a Bedrock resource pack from the server when it should be downloading it
|
||||
* from a remote provider. Since this would be called each time a Bedrock client requests a piece of the Bedrock pack,
|
||||
* this uses a cache to ensure we aren't re-checking a dozen times.
|
||||
*
|
||||
* @param codec the codec of the resource pack that wasn't successfully downloaded by a Bedrock client.
|
||||
*/
|
||||
public static void testRemotePack(GeyserSession session, UrlPackCodec codec, String packId, String packVersion) {
|
||||
if (CACHED_FAILED_PACKS.getIfPresent(codec.url()) == null) {
|
||||
String url = codec.url();
|
||||
CACHED_FAILED_PACKS.put(url, codec);
|
||||
GeyserImpl.getInstance().getLogger().warning(
|
||||
"A Bedrock client (%s, playing on %s / %s) was not able to download the resource pack at %s. Checking for changes now:"
|
||||
.formatted(session.bedrockUsername(), session.getClientData().getDeviceOs().name(), session.getClientData().getDeviceId(), codec.url())
|
||||
);
|
||||
|
||||
downloadPack(codec.url(), true).whenComplete((pathPackCodec, e) -> {
|
||||
if (e != null) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
|
||||
if (GeyserImpl.getInstance().getLogger().isDebug()) {
|
||||
e.printStackTrace();
|
||||
if (pathPackCodec != null) {
|
||||
deleteFile(pathPackCodec.path());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathPackCodec == null) {
|
||||
return; // Already warned about
|
||||
}
|
||||
|
||||
ResourcePack newPack = ResourcePackLoader.readPack(pathPackCodec.path());
|
||||
UUID newUUID = newPack.manifest().header().uuid();
|
||||
if (newUUID.toString().equals(packId)) {
|
||||
if (packVersion.equals(newPack.manifest().header().version().toString())) {
|
||||
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
|
||||
.formatted(packVersion, newPack.manifest().header().version().toString(), url));
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
|
||||
}
|
||||
|
||||
// This should be safe to do as we're not directly using registries to read packs.
|
||||
// Instead, they're cached per-session in the SessionLoadResourcePacks event
|
||||
Registries.RESOURCE_PACKS.get().remove(packId);
|
||||
Registries.RESOURCE_PACKS.get().put(newUUID.toString(), newPack);
|
||||
|
||||
if (codec instanceof GeyserUrlPackCodec geyserUrlPackCodec && geyserUrlPackCodec.getFallback() != null) {
|
||||
// Other implementations could, in theory, not have a fallback
|
||||
Path path = geyserUrlPackCodec.getFallback().path();
|
||||
try {
|
||||
GeyserImpl.getInstance().getScheduledThread().schedule(() -> {
|
||||
CACHED_FAILED_PACKS.invalidate(packId);
|
||||
deleteFile(path);
|
||||
}, 5, TimeUnit.MINUTES);
|
||||
} catch (RejectedExecutionException exception) {
|
||||
// No scheduling here, probably because we're shutting down?
|
||||
deleteFile(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFile(Path path) {
|
||||
if (path.toFile().exists()) {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to delete old pack! " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<@Nullable PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Path path = WebUtils.downloadRemotePack(url, testing);
|
||||
|
||||
// Already warned about these above
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the pack is a .zip or .mcpack file
|
||||
if (!PACK_MATCHER.matches(path)) {
|
||||
throw new IllegalArgumentException("Invalid pack format from url %s! Not a .zip or .mcpack file.".formatted(url));
|
||||
}
|
||||
|
||||
try {
|
||||
try (ZipFile zip = new ZipFile(path.toFile())) {
|
||||
if (zip.stream().noneMatch(x -> x.getName().contains("manifest.json"))) {
|
||||
throw new IllegalArgumentException("The pack at the url " + url + " does not contain a manifest file!");
|
||||
}
|
||||
|
||||
// Check if a "manifest.json" or "pack_manifest.json" file is located directly in the zip... does not work otherwise.
|
||||
// (something like MyZip.zip/manifest.json) will not, but will if it's a subfolder (MyPack.zip/MyPack/manifest.json)
|
||||
if (zip.getEntry("manifest.json") != null || zip.getEntry("pack_manifest.json") != null) {
|
||||
if (GeyserImpl.getInstance().getLogger().isDebug()) {
|
||||
GeyserImpl.getInstance().getLogger().info("The remote resource pack from " + url + " contains a manifest.json file at the root of the zip file. " +
|
||||
"This may not work for remote packs, and could cause Bedrock clients to fall back to request the pack from the server. " +
|
||||
"Please put the pack file in a subfolder, and provide that zip in the URL.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
|
||||
}
|
||||
|
||||
return new GeyserPathPackCodec(path);
|
||||
});
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
CACHED_FAILED_PACKS.invalidateAll();
|
||||
|
||||
}
|
||||
|
||||
public static void cleanupRemotePacks() {
|
||||
File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs").toFile();
|
||||
if (!cacheFolder.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
final long expireTime = (((long) 1000 * 60 * 60)); // one hour
|
||||
for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) {
|
||||
if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
imageFile.delete();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
GeyserImpl.getInstance().getLogger().debug(String.format("Removed %d cached resource pack files as they are no longer in use!", count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ import java.util.stream.Collectors;
|
||||
import static org.geysermc.geyser.scoreboard.UpdateType.*;
|
||||
|
||||
public final class Scoreboard {
|
||||
private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true"));
|
||||
private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "false"));
|
||||
private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(System.getProperty("Geyser.AddTeamSuggestions", "true"));
|
||||
|
||||
private final GeyserSession session;
|
||||
|
@ -48,7 +48,7 @@ public final class ScoreboardUpdater extends Thread {
|
||||
static {
|
||||
GeyserConfiguration config = GeyserImpl.getInstance().getConfig();
|
||||
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
|
||||
DEBUG_ENABLED = config.isDebugMode();
|
||||
DEBUG_ENABLED = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "false")) && config.isDebugMode();
|
||||
}
|
||||
|
||||
private final GeyserImpl geyser = GeyserImpl.getInstance();
|
||||
|
@ -167,7 +167,7 @@ public class SkinProvider {
|
||||
if (count > 0) {
|
||||
GeyserImpl.getInstance().getLogger().debug(String.format("Removed %d cached image files as they have expired", count));
|
||||
}
|
||||
}, 10, 1440, TimeUnit.MINUTES);
|
||||
}, 10, 1, TimeUnit.DAYS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,22 +28,26 @@ package org.geysermc.geyser.util;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WebUtils {
|
||||
|
||||
private static final Path REMOTE_PACK_CACHE = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
|
||||
|
||||
/**
|
||||
* Makes a web request to the given URL and returns the body as a string
|
||||
*
|
||||
@ -96,6 +100,114 @@ public class WebUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a remote pack URL to see if it is valid
|
||||
* If it is, it will download the pack file and return a path to it
|
||||
*
|
||||
* @param url The URL to check
|
||||
* @param force If true, the pack will be downloaded even if it is cached to a separate location.
|
||||
* @return Path to the downloaded pack file, or null if it was unable to be loaded
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static @Nullable Path downloadRemotePack(String url, boolean force) {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
try {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
|
||||
|
||||
con.setConnectTimeout(10000);
|
||||
con.setReadTimeout(10000);
|
||||
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION);
|
||||
con.setInstanceFollowRedirects(true);
|
||||
|
||||
int responseCode = con.getResponseCode();
|
||||
if (responseCode >= 400) {
|
||||
throw new IllegalStateException(String.format("Invalid response code from remote pack at URL: %s (code: %d)", url, responseCode));
|
||||
}
|
||||
|
||||
int size = con.getContentLength();
|
||||
String type = con.getContentType();
|
||||
|
||||
if (size <= 0) {
|
||||
throw new IllegalArgumentException(String.format("Invalid content length received from remote pack at URL: %s (size: %d)", url, size));
|
||||
}
|
||||
|
||||
// This doesn't seem to be a requirement (anymore?). Logging to debug as it might be interesting though.
|
||||
if (type == null || !type.equals("application/zip")) {
|
||||
logger.warning(String.format("Application type received from remote pack at URL %s uses the content type: %s! This may result in packs not loading " +
|
||||
"for Bedrock players.", url, type));
|
||||
}
|
||||
|
||||
Path packMetadata = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".metadata");
|
||||
Path downloadLocation;
|
||||
|
||||
// If we downloaded this pack before, reuse it if the ETag matches.
|
||||
if (Files.exists(packMetadata) && !force) {
|
||||
try {
|
||||
List<String> metadata = Files.readAllLines(packMetadata, StandardCharsets.UTF_8);
|
||||
int cachedSize = Integer.parseInt(metadata.get(0));
|
||||
String cachedEtag = metadata.get(1);
|
||||
long cachedLastModified = Long.parseLong(metadata.get(2));
|
||||
downloadLocation = REMOTE_PACK_CACHE.resolve(metadata.get(3));
|
||||
|
||||
if (cachedSize == size && cachedEtag.equals(con.getHeaderField("ETag")) &&
|
||||
cachedLastModified == con.getLastModified() && downloadLocation.toFile().exists()) {
|
||||
logger.debug("Using cached pack (%s) for %s.".formatted(downloadLocation.getFileName(), url));
|
||||
downloadLocation.toFile().setLastModified(System.currentTimeMillis());
|
||||
packMetadata.toFile().setLastModified(System.currentTimeMillis());
|
||||
return downloadLocation;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read cached pack metadata! " + e);
|
||||
try {
|
||||
Files.delete(packMetadata);
|
||||
} catch (Exception exception) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to delete pack metadata!", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + "_" + System.currentTimeMillis() + ".zip");
|
||||
Files.copy(con.getInputStream(), downloadLocation, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// This needs to match as the client fails to download the pack otherwise
|
||||
long downloadSize = Files.size(downloadLocation);
|
||||
if (downloadSize != size) {
|
||||
Files.delete(downloadLocation);
|
||||
throw new IllegalStateException("Size mismatch with resource pack at url: %s. Downloaded pack has %s bytes, expected %s bytes!"
|
||||
.formatted(url, downloadSize, size));
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(
|
||||
packMetadata,
|
||||
Arrays.asList(
|
||||
String.valueOf(size),
|
||||
con.getHeaderField("ETag"),
|
||||
String.valueOf(con.getLastModified()),
|
||||
downloadLocation.getFileName().toString()
|
||||
));
|
||||
packMetadata.toFile().setLastModified(System.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to write cached pack metadata: " + e.getMessage());
|
||||
Files.delete(packMetadata);
|
||||
Files.delete(downloadLocation);
|
||||
return null;
|
||||
}
|
||||
|
||||
downloadLocation.toFile().setLastModified(System.currentTimeMillis());
|
||||
return downloadLocation;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Unable to download resource pack from malformed URL %s! ".formatted(url));
|
||||
} catch (SocketTimeoutException | ConnectException e) {
|
||||
logger.error("Unable to download pack from url %s due to network error! ( %s )".formatted(url, e.getMessage()));
|
||||
logger.debug(e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to download and save remote resource pack from: %s ( %s )!".formatted(url, e.getMessage()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Post a string to the given URL
|
||||
*
|
||||
|
@ -167,6 +167,14 @@ above-bedrock-nether-building: false
|
||||
# want to download the resource packs.
|
||||
force-resource-packs: true
|
||||
|
||||
# A list of links to send to the client to download resource packs from.
|
||||
# These must be direct links to the resource pack, not a link to a page containing the resource pack.
|
||||
# If you enter a link here, Geyser will download the resource pack once to check if it's in a valid format.
|
||||
# See https://wiki.geysermc.org/geyser/packs for more info.
|
||||
resource-pack-urls:
|
||||
# GeyserOptionalPack
|
||||
- "https://download.geysermc.org/v2/projects/geyseroptionalpack/versions/latest/builds/latest/downloads/geyseroptionalpack"
|
||||
|
||||
# Allows Xbox achievements to be unlocked.
|
||||
xbox-achievements-enabled: false
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren