mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Exposing resourcepack loading to api (#3696)
Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> Co-authored-by: RednedEpic <redned235@gmail.com>
This commit is contained in:
parent
6eca6ade06
commit
903e61f1a3
27 changed files with 1334 additions and 347 deletions
|
@ -36,6 +36,7 @@ import org.geysermc.geyser.api.extension.ExtensionManager;
|
||||||
import org.geysermc.geyser.api.network.BedrockListener;
|
import org.geysermc.geyser.api.network.BedrockListener;
|
||||||
import org.geysermc.geyser.api.network.RemoteServer;
|
import org.geysermc.geyser.api.network.RemoteServer;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -107,6 +108,22 @@ public interface GeyserApi extends GeyserApiBase {
|
||||||
@NonNull
|
@NonNull
|
||||||
BedrockListener bedrockListener();
|
BedrockListener bedrockListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Path} to the Geyser config directory.
|
||||||
|
*
|
||||||
|
* @return the path to the Geyser config directory
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Path configDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link Path} to the Geyser packs directory.
|
||||||
|
*
|
||||||
|
* @return the path to the Geyser packs directory
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Path packDirectory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current {@link GeyserApiBase} instance.
|
* Gets the current {@link GeyserApiBase} instance.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.event.bedrock;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||||
|
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Geyser initializes a session for a new Bedrock client and is in the process of sending resource packs.
|
||||||
|
*/
|
||||||
|
public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
|
||||||
|
public SessionLoadResourcePacksEvent(@NonNull GeyserConnection connection) {
|
||||||
|
super(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an unmodifiable list of {@link ResourcePack}s that will be sent to the client.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable list of resource packs that will be sent to the client.
|
||||||
|
*/
|
||||||
|
public abstract @NonNull List<ResourcePack> resourcePacks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a {@link ResourcePack} to be sent to the client.
|
||||||
|
*
|
||||||
|
* @param resourcePack a resource pack that will be sent to the client.
|
||||||
|
* @return true if the resource pack was added successfully,
|
||||||
|
* or false if already present
|
||||||
|
*/
|
||||||
|
public abstract boolean register(@NonNull ResourcePack resourcePack);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a resource pack from being sent to the client.
|
||||||
|
*
|
||||||
|
* @param uuid the UUID of the resource pack
|
||||||
|
* @return true whether the resource pack was removed from the list of resource packs.
|
||||||
|
*/
|
||||||
|
public abstract boolean unregister(@NonNull UUID uuid);
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.pack;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.geysermc.geyser.api.GeyserApi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a pack codec that can be used
|
||||||
|
* to provide resource packs to clients.
|
||||||
|
*/
|
||||||
|
public abstract class PackCodec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the sha256 hash of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the hash of the resource pack
|
||||||
|
*/
|
||||||
|
public abstract byte @NonNull [] sha256();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource pack size.
|
||||||
|
*
|
||||||
|
* @return the resource pack file size
|
||||||
|
*/
|
||||||
|
public abstract long size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the given resource pack into a byte buffer.
|
||||||
|
*
|
||||||
|
* @param resourcePack the resource pack to serialize
|
||||||
|
* @return the serialized resource pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new resource pack from this codec.
|
||||||
|
*
|
||||||
|
* @return the new resource pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected abstract ResourcePack create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pack provider from the given path.
|
||||||
|
*
|
||||||
|
* @param path the path to create the pack provider from
|
||||||
|
* @return the new pack provider
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static PackCodec path(@NonNull Path path) {
|
||||||
|
return GeyserApi.api().provider(PathPackCodec.class, path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.pack;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a pack codec that creates a resource
|
||||||
|
* pack from a path on the filesystem.
|
||||||
|
*/
|
||||||
|
public abstract class PathPackCodec extends PackCodec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the path of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the path of the resource pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract Path path();
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.pack;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resource pack sent to Bedrock clients
|
||||||
|
* <p>
|
||||||
|
* This representation of a resource pack only contains what
|
||||||
|
* Geyser requires to send it to the client.
|
||||||
|
*/
|
||||||
|
public interface ResourcePack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link PackCodec codec} for this pack.
|
||||||
|
*
|
||||||
|
* @return the codec for this pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
PackCodec codec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource pack manifest.
|
||||||
|
*
|
||||||
|
* @return the resource pack manifest
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
ResourcePackManifest manifest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the content key of the resource pack. Lack of a content key is represented by an empty String.
|
||||||
|
*
|
||||||
|
* @return the content key of the resource pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String contentKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a resource pack with the given {@link PackCodec}.
|
||||||
|
*
|
||||||
|
* @param codec the pack codec
|
||||||
|
* @return the resource pack
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
static ResourcePack create(@NonNull PackCodec codec) {
|
||||||
|
return codec.create();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.pack;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resource pack manifest.
|
||||||
|
*/
|
||||||
|
public interface ResourcePackManifest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the format version of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the format version
|
||||||
|
*/
|
||||||
|
int formatVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the header of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the header
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Header header();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the modules of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the modules
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Collection<? extends Module> modules();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the dependencies of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the dependencies
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Collection<? extends Dependency> dependencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the header of a resource pack.
|
||||||
|
*/
|
||||||
|
interface Header {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the UUID of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the UUID
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
UUID uuid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the version
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Version version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the name
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String description();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the minimum supported Minecraft version of the resource pack.
|
||||||
|
*
|
||||||
|
* @return the minimum supported Minecraft version
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Version minimumSupportedMinecraftVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a module of a resource pack.
|
||||||
|
*/
|
||||||
|
interface Module {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the UUID of the module.
|
||||||
|
*
|
||||||
|
* @return the UUID
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
UUID uuid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version of the module.
|
||||||
|
*
|
||||||
|
* @return the version
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Version version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the type of the module.
|
||||||
|
*
|
||||||
|
* @return the type
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String type();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the module.
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String description();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a dependency of a resource pack.
|
||||||
|
*/
|
||||||
|
interface Dependency {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the UUID of the dependency.
|
||||||
|
*
|
||||||
|
* @return the uuid
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
UUID uuid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version of the dependency.
|
||||||
|
*
|
||||||
|
* @return the version
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Version version();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a version of a resource pack.
|
||||||
|
*/
|
||||||
|
interface Version {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the major version.
|
||||||
|
*
|
||||||
|
* @return the major version
|
||||||
|
*/
|
||||||
|
int major();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the minor version.
|
||||||
|
*
|
||||||
|
* @return the minor version
|
||||||
|
*/
|
||||||
|
int minor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the patch version.
|
||||||
|
*
|
||||||
|
* @return the patch version
|
||||||
|
*/
|
||||||
|
int patch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version formatted as a String.
|
||||||
|
*
|
||||||
|
* @return the version string
|
||||||
|
*/
|
||||||
|
@NonNull String toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.raknet) {
|
implementation(libs.raknet) {
|
||||||
exclude("io.netty", "*");
|
exclude("io.netty", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.netty.resolver.dns)
|
implementation(libs.netty.resolver.dns)
|
||||||
|
|
|
@ -69,9 +69,9 @@ import org.geysermc.geyser.event.GeyserEventBus;
|
||||||
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||||
import org.geysermc.geyser.level.WorldManager;
|
import org.geysermc.geyser.level.WorldManager;
|
||||||
import org.geysermc.geyser.network.netty.GeyserServer;
|
import org.geysermc.geyser.network.netty.GeyserServer;
|
||||||
import org.geysermc.geyser.pack.ResourcePack;
|
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
|
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||||
|
@ -90,6 +90,7 @@ import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -258,7 +259,7 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
SkinProvider.registerCacheImageTask(this);
|
SkinProvider.registerCacheImageTask(this);
|
||||||
|
|
||||||
ResourcePack.loadPacks();
|
Registries.RESOURCE_PACKS.load();
|
||||||
|
|
||||||
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
|
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
|
||||||
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
|
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
|
||||||
|
@ -622,7 +623,7 @@ public class GeyserImpl implements GeyserApi {
|
||||||
this.erosionUnixListener.close();
|
this.erosionUnixListener.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourcePack.PACKS.clear();
|
Registries.RESOURCE_PACKS.get().clear();
|
||||||
|
|
||||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||||
this.extensionManager.disableExtensions();
|
this.extensionManager.disableExtensions();
|
||||||
|
@ -681,6 +682,18 @@ public class GeyserImpl implements GeyserApi {
|
||||||
return getConfig().getBedrock();
|
return getConfig().getBedrock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Path configDirectory() {
|
||||||
|
return bootstrap.getConfigFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Path packDirectory() {
|
||||||
|
return bootstrap.getConfigFolder().resolve("packs");
|
||||||
|
}
|
||||||
|
|
||||||
public int buildNumber() {
|
public int buildNumber() {
|
||||||
if (!this.isProductionEnvironment()) {
|
if (!this.isProductionEnvironment()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.event.type;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
|
||||||
|
|
||||||
|
private final Map<String, ResourcePack> packs;
|
||||||
|
|
||||||
|
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
|
||||||
|
super(session);
|
||||||
|
this.packs = packMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Map<String, ResourcePack> getPacks() {
|
||||||
|
return packs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull List<ResourcePack> resourcePacks() {
|
||||||
|
return List.copyOf(packs.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean register(@NonNull ResourcePack resourcePack) {
|
||||||
|
String packID = resourcePack.manifest().header().uuid().toString();
|
||||||
|
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unregister(@NonNull UUID uuid) {
|
||||||
|
return packs.remove(uuid.toString()) != null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,9 +49,12 @@ import org.cloudburstmc.protocol.common.PacketSignal;
|
||||||
import org.geysermc.geyser.Constants;
|
import org.geysermc.geyser.Constants;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
|
import org.geysermc.geyser.api.pack.PackCodec;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.geyser.pack.ResourcePack;
|
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
|
||||||
import org.geysermc.geyser.pack.ResourcePackManifest;
|
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
@ -61,16 +64,20 @@ import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||||
import org.geysermc.geyser.util.MathUtils;
|
import org.geysermc.geyser.util.MathUtils;
|
||||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
|
|
||||||
private boolean networkSettingsRequested = false;
|
private boolean networkSettingsRequested = false;
|
||||||
private Deque<String> packsToSent = new ArrayDeque<>();
|
private final Deque<String> packsToSent = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
|
||||||
|
|
||||||
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
|
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
|
||||||
super(geyser, session);
|
super(geyser, session);
|
||||||
|
@ -172,12 +179,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
|
|
||||||
geyser.getSessionManager().addPendingSession(session);
|
geyser.getSessionManager().addPendingSession(session);
|
||||||
|
|
||||||
|
this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()));
|
||||||
|
this.geyser.eventBus().fire(this.resourcePackLoadEvent);
|
||||||
|
|
||||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||||
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
|
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||||
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
|
PackCodec codec = pack.codec();
|
||||||
|
ResourcePackManifest.Header header = pack.manifest().header();
|
||||||
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
|
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
|
||||||
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(),
|
header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
|
||||||
resourcePack.getContentKey(), "", header.getUuid().toString(), false, false));
|
"", header.uuid().toString(), false, false));
|
||||||
}
|
}
|
||||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||||
session.sendUpstreamPacket(resourcePacksInfo);
|
session.sendUpstreamPacket(resourcePacksInfo);
|
||||||
|
@ -210,9 +221,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
|
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
|
||||||
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
||||||
|
|
||||||
for (ResourcePack pack : ResourcePack.PACKS.values()) {
|
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
ResourcePackManifest.Header header = pack.manifest().header();
|
||||||
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
|
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||||
|
@ -291,21 +302,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
@Override
|
@Override
|
||||||
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
|
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
|
||||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||||
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
|
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString());
|
||||||
|
PackCodec codec = pack.codec();
|
||||||
|
|
||||||
data.setChunkIndex(packet.getChunkIndex());
|
data.setChunkIndex(packet.getChunkIndex());
|
||||||
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
|
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||||
data.setPackVersion(packet.getPackVersion());
|
data.setPackVersion(packet.getPackVersion());
|
||||||
data.setPackId(packet.getPackId());
|
data.setPackId(packet.getPackId());
|
||||||
|
|
||||||
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
|
int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE;
|
||||||
long remainingSize = pack.getFile().length() - offset;
|
long remainingSize = codec.size() - offset;
|
||||||
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)];
|
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)];
|
||||||
|
|
||||||
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
|
try (SeekableByteChannel channel = codec.serialize(pack)) {
|
||||||
inputStream.skip(offset);
|
channel.position(offset);
|
||||||
inputStream.read(packData, 0, packData.length);
|
channel.read(ByteBuffer.wrap(packData, 0, packData.length));
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +326,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
session.sendUpstreamPacket(data);
|
session.sendUpstreamPacket(data);
|
||||||
|
|
||||||
// Check if it is the last chunk and send next pack in queue when available.
|
// Check if it is the last chunk and send next pack in queue when available.
|
||||||
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||||
sendPackDataInfo(packsToSent.pop());
|
sendPackDataInfo(packsToSent.pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,15 +336,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
private void sendPackDataInfo(String id) {
|
private void sendPackDataInfo(String id) {
|
||||||
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
|
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
|
||||||
String[] packID = id.split("_");
|
String[] packID = id.split("_");
|
||||||
ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
|
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
|
||||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
PackCodec codec = pack.codec();
|
||||||
|
ResourcePackManifest.Header header = pack.manifest().header();
|
||||||
|
|
||||||
data.setPackId(header.getUuid());
|
data.setPackId(header.uuid());
|
||||||
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
|
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
|
||||||
data.setChunkCount(chunkCount);
|
data.setChunkCount(chunkCount);
|
||||||
data.setCompressedPackSize(pack.getFile().length());
|
data.setCompressedPackSize(codec.size());
|
||||||
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
|
data.setMaxChunkSize(GeyserResourcePack.CHUNK_SIZE);
|
||||||
data.setHash(pack.getSha256());
|
data.setHash(codec.sha256());
|
||||||
data.setPackVersion(packID[1]);
|
data.setPackVersion(packID[1]);
|
||||||
data.setPremium(false);
|
data.setPremium(false);
|
||||||
data.setType(ResourcePackType.RESOURCES);
|
data.setType(ResourcePackType.RESOURCES);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.pack;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.api.pack.PackCodec;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||||
|
|
||||||
|
public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of each chunk to use when sending the resource packs to clients in bytes
|
||||||
|
*/
|
||||||
|
public static final int CHUNK_SIZE = 102400;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.pack;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection<Module> modules, Collection<Dependency> dependencies) implements ResourcePackManifest {
|
||||||
|
|
||||||
|
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
|
||||||
|
|
||||||
|
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
|
||||||
|
|
||||||
|
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
|
||||||
|
|
||||||
|
@JsonDeserialize(using = Version.VersionDeserializer.class)
|
||||||
|
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return major + "." + minor + "." + patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VersionDeserializer extends JsonDeserializer<Version> {
|
||||||
|
@Override
|
||||||
|
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
int[] version = ctxt.readValue(p, int[].class);
|
||||||
|
return new Version(version[0], version[1], version[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.geyser.pack;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
|
||||||
import org.geysermc.geyser.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This represents a resource pack and all the data relevant to it
|
|
||||||
*/
|
|
||||||
public class ResourcePack {
|
|
||||||
/**
|
|
||||||
* The list of loaded resource packs
|
|
||||||
*/
|
|
||||||
public static final Map<String, ResourcePack> PACKS = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of each chunk to use when sending the resource packs to clients in bytes
|
|
||||||
*/
|
|
||||||
public static final int CHUNK_SIZE = 102400;
|
|
||||||
|
|
||||||
private byte[] sha256;
|
|
||||||
private File file;
|
|
||||||
private ResourcePackManifest manifest;
|
|
||||||
private ResourcePackManifest.Version version;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private String contentKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop through the packs directory and locate valid resource pack files
|
|
||||||
*/
|
|
||||||
public static void loadPacks() {
|
|
||||||
Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs");
|
|
||||||
|
|
||||||
if (!Files.exists(directory)) {
|
|
||||||
try {
|
|
||||||
Files.createDirectory(directory);
|
|
||||||
} catch (IOException e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// As we just created the directory it will be empty
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Path> resourcePacks;
|
|
||||||
try {
|
|
||||||
resourcePacks = Files.walk(directory).collect(Collectors.toList());
|
|
||||||
} catch (IOException e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
|
||||||
GeyserImpl.getInstance().eventBus().fire(event);
|
|
||||||
|
|
||||||
for (Path path : event.resourcePacks()) {
|
|
||||||
File file = path.toFile();
|
|
||||||
|
|
||||||
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
|
|
||||||
ResourcePack pack = new ResourcePack();
|
|
||||||
|
|
||||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
|
||||||
|
|
||||||
try (ZipFile zip = new ZipFile(file);
|
|
||||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
|
||||||
stream.forEach((x) -> {
|
|
||||||
String name = x.getName();
|
|
||||||
if (name.length() >= 80) {
|
|
||||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
|
|
||||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
|
||||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
|
||||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
|
||||||
}
|
|
||||||
if (name.contains("manifest.json")) {
|
|
||||||
try {
|
|
||||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
|
||||||
// Sometimes a pack_manifest file is present and not in a valid format,
|
|
||||||
// but a manifest file is, so we null check through that one
|
|
||||||
if (manifest.getHeader().getUuid() != null) {
|
|
||||||
pack.file = file;
|
|
||||||
pack.manifest = manifest;
|
|
||||||
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
|
|
||||||
|
|
||||||
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
|
||||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
|
||||||
File keyFile = new File(file.getParentFile(), file.getName() + ".key");
|
|
||||||
pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : "";
|
|
||||||
} catch (Exception e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSha256() {
|
|
||||||
return sha256;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourcePackManifest getManifest() {
|
|
||||||
return manifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourcePackManifest.Version getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.geyser.pack;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* author: NukkitX
|
|
||||||
* Nukkit Project
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode
|
|
||||||
public class ResourcePackManifest {
|
|
||||||
@JsonProperty("format_version")
|
|
||||||
private Integer formatVersion;
|
|
||||||
private Header header;
|
|
||||||
private Collection<Module> modules;
|
|
||||||
protected Collection<Dependency> dependencies;
|
|
||||||
|
|
||||||
public Collection<Module> getModules() {
|
|
||||||
return Collections.unmodifiableCollection(modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public static class Header {
|
|
||||||
private String description;
|
|
||||||
private String name;
|
|
||||||
private UUID uuid;
|
|
||||||
private int[] version;
|
|
||||||
@JsonProperty("min_engine_version")
|
|
||||||
private int[] minimumSupportedMinecraftVersion;
|
|
||||||
|
|
||||||
public String getVersionString() {
|
|
||||||
return version[0] + "." + version[1] + "." + version[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public static class Module {
|
|
||||||
private String description;
|
|
||||||
private String name;
|
|
||||||
private UUID uuid;
|
|
||||||
private int[] version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public static class Dependency {
|
|
||||||
private UUID uuid;
|
|
||||||
private int[] version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
public static class Version {
|
|
||||||
private final int major;
|
|
||||||
private final int minor;
|
|
||||||
private final int patch;
|
|
||||||
|
|
||||||
public static Version fromString(String ver) {
|
|
||||||
String[] split = ver.replace(']', ' ')
|
|
||||||
.replace('[', ' ')
|
|
||||||
.replaceAll(" ", "").split(",");
|
|
||||||
|
|
||||||
return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Version fromArray(int[] ver) {
|
|
||||||
return new Version(ver[0], ver[1], ver[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Version(int major, int minor, int patch) {
|
|
||||||
this.major = major;
|
|
||||||
this.minor = minor;
|
|
||||||
this.patch = patch;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return major + "." + minor + "." + patch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.pack.path;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||||
|
import org.geysermc.geyser.util.FileUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GeyserPathPackCodec extends PathPackCodec {
|
||||||
|
private final Path path;
|
||||||
|
private FileTime lastModified;
|
||||||
|
|
||||||
|
private byte[] sha256;
|
||||||
|
private long size = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Path path() {
|
||||||
|
this.checkLastModified();
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte @NonNull [] sha256() {
|
||||||
|
this.checkLastModified();
|
||||||
|
if (this.sha256 != null) {
|
||||||
|
return this.sha256;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sha256 = FileUtils.calculateSHA256(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() {
|
||||||
|
this.checkLastModified();
|
||||||
|
if (this.size != -1) {
|
||||||
|
return this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.size = Files.size(this.path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not get file size of path " + this.path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
|
||||||
|
return FileChannel.open(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull ResourcePack create() {
|
||||||
|
return ResourcePackLoader.readPack(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLastModified() {
|
||||||
|
try {
|
||||||
|
FileTime lastModified = Files.getLastModifiedTime(this.path);
|
||||||
|
if (this.lastModified == null) {
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastModified.toInstant().isAfter(this.lastModified.toInstant())) {
|
||||||
|
GeyserImpl.getInstance().getLogger().warning("Detected a change in the resource pack " + path + ". This is likely to cause undefined behavior for new clients joining. It is suggested you restart Geyser.");
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
this.sha256 = null;
|
||||||
|
this.size = -1;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.registry;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.registry.loader.RegistryLoader;
|
||||||
|
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deferred registry is a registry that is not loaded until it is needed.
|
||||||
|
* This is useful for registries that are not needed until after other parts
|
||||||
|
* of the lifecycle have been completed.
|
||||||
|
* <p>
|
||||||
|
* This class is slightly different from other registries in that it acts as
|
||||||
|
* a wrapper around another registry. This is to allow for any kind of registry
|
||||||
|
* type to be deferred.
|
||||||
|
*
|
||||||
|
* @param <M> the value being held by the registry
|
||||||
|
*/
|
||||||
|
public final class DeferredRegistry<M> implements IRegistry<M> {
|
||||||
|
private final Registry<M> backingRegistry;
|
||||||
|
private final Supplier<M> loader;
|
||||||
|
|
||||||
|
private boolean loaded;
|
||||||
|
|
||||||
|
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||||
|
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||||
|
this.loader = () -> deferredLoader.load(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||||
|
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||||
|
this.loader = () -> deferredLoader.get().load(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||||
|
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||||
|
this.loader = () -> deferredLoader.load(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||||
|
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||||
|
this.loader = () -> deferredLoader.get().load(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M get() {
|
||||||
|
if (!this.loaded) {
|
||||||
|
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.backingRegistry.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(M mappings) {
|
||||||
|
if (!this.loaded) {
|
||||||
|
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.backingRegistry.set(mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(Consumer<M> consumer) {
|
||||||
|
if (!this.loaded) {
|
||||||
|
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.backingRegistry.register(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the registry.
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
if (this.loaded) {
|
||||||
|
throw new IllegalStateException("Registry has already been loaded!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.backingRegistry.set(this.loader.get());
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new deferred registry.
|
||||||
|
*
|
||||||
|
* @param registryLoader the registry loader
|
||||||
|
* @param deferredLoader the deferred loader
|
||||||
|
* @param <I> the input type
|
||||||
|
* @param <M> the registry type
|
||||||
|
* @return the new deferred registry
|
||||||
|
*/
|
||||||
|
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||||
|
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new deferred registry.
|
||||||
|
*
|
||||||
|
* @param registryLoader the registry loader
|
||||||
|
* @param deferredLoader the deferred loader
|
||||||
|
* @param <I> the input type
|
||||||
|
* @param <M> the registry type
|
||||||
|
* @return the new deferred registry
|
||||||
|
*/
|
||||||
|
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||||
|
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new deferred registry.
|
||||||
|
*
|
||||||
|
* @param registryInitializer the registry initializer
|
||||||
|
* @param deferredLoader the deferred loader
|
||||||
|
* @param <I> the input type
|
||||||
|
* @param <M> the registry type
|
||||||
|
* @return the new deferred registry
|
||||||
|
*/
|
||||||
|
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||||
|
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new deferred registry.
|
||||||
|
*
|
||||||
|
* @param registryInitializer the registry initializer
|
||||||
|
* @param deferredLoader the deferred loader
|
||||||
|
* @param <I> the input type
|
||||||
|
* @param <M> the registry type
|
||||||
|
* @return the new deferred registry
|
||||||
|
*/
|
||||||
|
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||||
|
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry initializer.
|
||||||
|
*
|
||||||
|
* @param <M> the registry type
|
||||||
|
*/
|
||||||
|
interface RegistryInitializer<M> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the registry.
|
||||||
|
*
|
||||||
|
* @param input the input
|
||||||
|
* @param registryLoader the registry loader
|
||||||
|
* @param <I> the input type
|
||||||
|
* @return the initialized registry
|
||||||
|
*/
|
||||||
|
<I> Registry<M> initialize(I input, RegistryLoader<I, M> registryLoader);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.registry;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a registry.
|
||||||
|
*
|
||||||
|
* @param <M> the value being held by the registry
|
||||||
|
*/
|
||||||
|
interface IRegistry<M> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the underlying value held by this registry.
|
||||||
|
*
|
||||||
|
* @return the underlying value held by this registry.
|
||||||
|
*/
|
||||||
|
M get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the underlying value held by this registry.
|
||||||
|
* Clears any existing data associated with the previous
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* @param mappings the underlying value held by this registry
|
||||||
|
*/
|
||||||
|
void set(M mappings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers what is specified in the given
|
||||||
|
* {@link Consumer} into the underlying value.
|
||||||
|
*
|
||||||
|
* @param consumer the consumer
|
||||||
|
*/
|
||||||
|
void register(Consumer<M> consumer);
|
||||||
|
}
|
|
@ -41,6 +41,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||||
|
@ -158,6 +160,11 @@ public final class Registries {
|
||||||
*/
|
*/
|
||||||
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
|
||||||
|
*/
|
||||||
|
public static final DeferredRegistry<Map<String, ResourcePack>> RESOURCE_PACKS = DeferredRegistry.create(GeyserImpl.getInstance().packDirectory(), SimpleMappedRegistry::create, RegistryLoaders.RESOURCE_PACKS);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
|
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -64,7 +64,7 @@ import java.util.function.Consumer;
|
||||||
*
|
*
|
||||||
* @param <M> the value being held by the registry
|
* @param <M> the value being held by the registry
|
||||||
*/
|
*/
|
||||||
public abstract class Registry<M> {
|
public abstract class Registry<M> implements IRegistry<M> {
|
||||||
protected M mappings;
|
protected M mappings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,6 +85,7 @@ public abstract class Registry<M> {
|
||||||
*
|
*
|
||||||
* @return the underlying value held by this registry.
|
* @return the underlying value held by this registry.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public M get() {
|
public M get() {
|
||||||
return this.mappings;
|
return this.mappings;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +97,7 @@ public abstract class Registry<M> {
|
||||||
*
|
*
|
||||||
* @param mappings the underlying value held by this registry
|
* @param mappings the underlying value held by this registry
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void set(M mappings) {
|
public void set(M mappings) {
|
||||||
this.mappings = mappings;
|
this.mappings = mappings;
|
||||||
}
|
}
|
||||||
|
@ -106,6 +108,7 @@ public abstract class Registry<M> {
|
||||||
*
|
*
|
||||||
* @param consumer the consumer
|
* @param consumer the consumer
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void register(Consumer<M> consumer) {
|
public void register(Consumer<M> consumer) {
|
||||||
consumer.accept(this.mappings);
|
consumer.accept(this.mappings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,16 @@ import org.geysermc.geyser.api.extension.Extension;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||||
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
||||||
|
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||||
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
||||||
|
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||||
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,11 +50,15 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||||
|
// misc
|
||||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||||
|
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||||
|
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||||
|
|
||||||
|
// items
|
||||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
|
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
|
||||||
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
|
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
|
||||||
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
|
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
|
||||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
|
||||||
|
|
||||||
return providers;
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,12 @@ public final class RegistryLoaders {
|
||||||
/**
|
/**
|
||||||
* The {@link RegistryLoader} responsible for loading NBT.
|
* The {@link RegistryLoader} responsible for loading NBT.
|
||||||
*/
|
*/
|
||||||
public static NbtRegistryLoader NBT = new NbtRegistryLoader();
|
public static final NbtRegistryLoader NBT = new NbtRegistryLoader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RegistryLoader} responsible for loading resource packs.
|
||||||
|
*/
|
||||||
|
public static final ResourcePackLoader RESOURCE_PACKS = new ResourcePackLoader();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
|
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
|
||||||
|
@ -51,10 +56,14 @@ public final class RegistryLoaders {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns a {@link RegistryLoader} which has not taken
|
||||||
|
* in any input value.
|
||||||
|
*
|
||||||
|
* @param <I> the input
|
||||||
* @param <V> the value
|
* @param <V> the value
|
||||||
* @return a RegistryLoader that is yet to contain a value.
|
* @return a RegistryLoader that is yet to contain a value.
|
||||||
*/
|
*/
|
||||||
public static <V> RegistryLoader<Object, V> uninitialized() {
|
public static <I, V> RegistryLoader<I, V> uninitialized() {
|
||||||
return input -> null;
|
return input -> null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.registry.loader;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||||
|
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
|
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||||
|
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
|
||||||
|
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||||
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
import org.geysermc.geyser.util.FileUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
|
||||||
|
*/
|
||||||
|
public class ResourcePackLoader implements RegistryLoader<Path, Map<String, ResourcePack>> {
|
||||||
|
|
||||||
|
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||||
|
|
||||||
|
private static final boolean SHOW_RESOURCE_PACK_LENGTH_WARNING = Boolean.parseBoolean(System.getProperty("Geyser.ShowResourcePackLengthWarning", "true"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop through the packs directory and locate valid resource pack files
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, ResourcePack> load(Path directory) {
|
||||||
|
Map<String, ResourcePack> packMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (!Files.exists(directory)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(directory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> resourcePacks;
|
||||||
|
try (Stream<Path> stream = Files.walk(directory)) {
|
||||||
|
resourcePacks = stream.filter(PACK_MATCHER::matches)
|
||||||
|
.collect(Collectors.toCollection(ArrayList::new)); // toList() does not guarantee mutability
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||||
|
|
||||||
|
// Ensure the event is fired even if there was an issue reading
|
||||||
|
// from our own resource pack directory. External projects may have
|
||||||
|
// resource packs located at different locations.
|
||||||
|
resourcePacks = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||||
|
GeyserImpl.getInstance().eventBus().fire(event);
|
||||||
|
|
||||||
|
for (Path path : event.resourcePacks()) {
|
||||||
|
try {
|
||||||
|
GeyserResourcePack pack = readPack(path);
|
||||||
|
packMap.put(pack.manifest().header().uuid().toString(), pack);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return packMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a resource pack at the given file. Also searches for a file in the same directory, with the same name
|
||||||
|
* but suffixed by ".key", containing the content key. If such file does not exist, no content key is stored.
|
||||||
|
*
|
||||||
|
* @param path the file to read from, in ZIP format
|
||||||
|
* @return a {@link ResourcePack} representation
|
||||||
|
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
|
||||||
|
*/
|
||||||
|
public static GeyserResourcePack readPack(Path path) throws IllegalArgumentException {
|
||||||
|
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
|
||||||
|
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
|
||||||
|
|
||||||
|
try (ZipFile zip = new ZipFile(path.toFile());
|
||||||
|
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||||
|
stream.forEach(x -> {
|
||||||
|
String name = x.getName();
|
||||||
|
if (SHOW_RESOURCE_PACK_LENGTH_WARNING && name.length() >= 80) {
|
||||||
|
GeyserImpl.getInstance().getLogger().warning("The resource pack " + path.getFileName()
|
||||||
|
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||||
|
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||||
|
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||||
|
}
|
||||||
|
if (name.contains("manifest.json")) {
|
||||||
|
try {
|
||||||
|
GeyserResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), GeyserResourcePackManifest.class);
|
||||||
|
if (manifest.header().uuid() != null) {
|
||||||
|
manifestReference.set(manifest);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GeyserResourcePackManifest manifest = manifestReference.get();
|
||||||
|
if (manifest == null) {
|
||||||
|
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||||
|
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||||
|
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
|
||||||
|
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
|
||||||
|
|
||||||
|
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,10 +30,9 @@ import org.geysermc.geyser.util.AssetUtils;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -80,14 +79,14 @@ public final class ProvidedSkins {
|
||||||
.resolve(slim ? "slim" : "wide");
|
.resolve(slim ? "slim" : "wide");
|
||||||
String assetName = asset.substring(asset.lastIndexOf('/') + 1);
|
String assetName = asset.substring(asset.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
File location = folder.resolve(assetName).toFile();
|
Path location = folder.resolve(assetName);
|
||||||
AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
AssetUtils.addTask(!Files.exists(location), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
||||||
(stream) -> AssetUtils.saveFile(location, stream),
|
(stream) -> AssetUtils.saveFile(location, stream),
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
// TODO lazy initialize?
|
// TODO lazy initialize?
|
||||||
BufferedImage image;
|
BufferedImage image;
|
||||||
try (InputStream stream = new FileInputStream(location)) {
|
try (InputStream stream = Files.newInputStream(location)) {
|
||||||
image = ImageIO.read(stream);
|
image = ImageIO.read(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.geysermc.geyser.util.WebUtils;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -57,8 +58,8 @@ public class MinecraftLocale {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ensureEN_US() {
|
public static void ensureEN_US() {
|
||||||
File localeFile = getFile("en_us");
|
Path localeFile = getPath("en_us");
|
||||||
AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
AssetUtils.addTask(!Files.exists(localeFile), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
||||||
(stream) -> AssetUtils.saveFile(localeFile, stream),
|
(stream) -> AssetUtils.saveFile(localeFile, stream),
|
||||||
() -> {
|
() -> {
|
||||||
if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
|
if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
|
||||||
|
@ -106,10 +107,10 @@ public class MinecraftLocale {
|
||||||
if (locale.equals("en_us")) {
|
if (locale.equals("en_us")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File localeFile = getFile(locale);
|
Path localeFile = getPath(locale);
|
||||||
|
|
||||||
// Check if we have already downloaded the locale file
|
// Check if we have already downloaded the locale file
|
||||||
if (localeFile.exists()) {
|
if (Files.exists(localeFile)) {
|
||||||
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
||||||
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
|
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
|
||||||
|
|
||||||
|
@ -130,8 +131,8 @@ public class MinecraftLocale {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getFile(String locale) {
|
private static Path getPath(String locale) {
|
||||||
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
|
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -192,8 +192,8 @@ public final class AssetUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveFile(File location, InputStream fileStream) throws IOException {
|
public static void saveFile(Path location, InputStream fileStream) throws IOException {
|
||||||
try (FileOutputStream outStream = new FileOutputStream(location)) {
|
try (OutputStream outStream = Files.newOutputStream(location)) {
|
||||||
|
|
||||||
// Write the file to the locale dir
|
// Write the file to the locale dir
|
||||||
byte[] buf = new byte[fileStream.available()];
|
byte[] buf = new byte[fileStream.available()];
|
||||||
|
|
|
@ -129,14 +129,14 @@ public class FileUtils {
|
||||||
/**
|
/**
|
||||||
* Calculate the SHA256 hash of a file
|
* Calculate the SHA256 hash of a file
|
||||||
*
|
*
|
||||||
* @param file File to calculate the hash for
|
* @param path Path to calculate the hash for
|
||||||
* @return A byte[] representation of the hash
|
* @return A byte[] representation of the hash
|
||||||
*/
|
*/
|
||||||
public static byte[] calculateSHA256(File file) {
|
public static byte[] calculateSHA256(Path path) {
|
||||||
byte[] sha256;
|
byte[] sha256;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sha256 = MessageDigest.getInstance("SHA-256").digest(readAllBytes(file));
|
sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(path));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Could not calculate pack hash", e);
|
throw new RuntimeException("Could not calculate pack hash", e);
|
||||||
}
|
}
|
||||||
|
@ -147,14 +147,14 @@ public class FileUtils {
|
||||||
/**
|
/**
|
||||||
* Calculate the SHA1 hash of a file
|
* Calculate the SHA1 hash of a file
|
||||||
*
|
*
|
||||||
* @param file File to calculate the hash for
|
* @param path Path to calculate the hash for
|
||||||
* @return A byte[] representation of the hash
|
* @return A byte[] representation of the hash
|
||||||
*/
|
*/
|
||||||
public static byte[] calculateSHA1(File file) {
|
public static byte[] calculateSHA1(Path path) {
|
||||||
byte[] sha1;
|
byte[] sha1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sha1 = MessageDigest.getInstance("SHA-1").digest(readAllBytes(file));
|
sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Could not calculate pack hash", e);
|
throw new RuntimeException("Could not calculate pack hash", e);
|
||||||
}
|
}
|
||||||
|
@ -162,20 +162,6 @@ public class FileUtils {
|
||||||
return sha1;
|
return sha1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An android compatible version of {@link Files#readAllBytes}
|
|
||||||
*
|
|
||||||
* @param file File to read bytes of
|
|
||||||
* @return The byte array of the file
|
|
||||||
*/
|
|
||||||
public static byte[] readAllBytes(File file) {
|
|
||||||
try (InputStream stream = new FileInputStream(file)) {
|
|
||||||
return stream.readAllBytes();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Cannot read " + file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource the internal resource to read off from
|
* @param resource the internal resource to read off from
|
||||||
* @return the byte array of an InputStream
|
* @return the byte array of an InputStream
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.registry.loader;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class ResourcePackLoaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPathMatcher() {
|
||||||
|
PathMatcher matcher = ResourcePackLoader.PACK_MATCHER;
|
||||||
|
|
||||||
|
assertTrue(matcher.matches(Path.of("pack.mcpack")));
|
||||||
|
assertTrue(matcher.matches(Path.of("pack.zip")));
|
||||||
|
assertTrue(matcher.matches(Path.of("packs", "pack.mcpack")));
|
||||||
|
assertTrue(matcher.matches(Path.of("packs", "category", "pack.mcpack")));
|
||||||
|
|
||||||
|
assertTrue(matcher.matches(Path.of("packs", "Resource+Pack+1.2.3.mcpack")));
|
||||||
|
assertTrue(matcher.matches(Path.of("Resource+Pack+1.2.3.mcpack")));
|
||||||
|
|
||||||
|
assertTrue(matcher.matches(Path.of("packs", "Resource+Pack+1.2.3.zip")));
|
||||||
|
assertTrue(matcher.matches(Path.of("Resource+Pack+1.2.3.zip")));
|
||||||
|
|
||||||
|
assertFalse(matcher.matches(Path.of("resource.pack")));
|
||||||
|
assertFalse(matcher.matches(Path.of("pack.7zip")));
|
||||||
|
assertFalse(matcher.matches(Path.of("packs")));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue