Support remote Geyser Bedrock pack URLs
Build and publish release / Build and publish RemotePackSync (push) Successful in 59s
Build and publish release / Build and publish RemotePackSync (push) Successful in 59s
This commit is contained in:
@@ -40,7 +40,12 @@ sha1Url: "https://cdn.example.com/server-pack.zip.sha1"
|
|||||||
# Seconds between Java SHA-1 and Bedrock pack checks. Minimum: 30.
|
# Seconds between Java SHA-1 and Bedrock pack checks. Minimum: 30.
|
||||||
refreshInterval: 300
|
refreshInterval: 300
|
||||||
|
|
||||||
# Optional local Bedrock Edition pack for GeyserMC players. Accepts .mcpack or .zip.
|
# Preferred: remote Bedrock pack URL for GeyserMC players.
|
||||||
|
# Must be a direct .mcpack/.zip download with Content-Type: application/zip
|
||||||
|
# and an exact Content-Length.
|
||||||
|
bedrockResourcePackUrl: "https://example.com/packs/server-pack.mcpack"
|
||||||
|
|
||||||
|
# Optional local alternative; use only when bedrockResourcePackUrl is blank.
|
||||||
# Relative paths are resolved from plugins/RemotePackSync/.
|
# Relative paths are resolved from plugins/RemotePackSync/.
|
||||||
bedrockResourcePack: "bedrock/server-pack.mcpack"
|
bedrockResourcePack: "bedrock/server-pack.mcpack"
|
||||||
```
|
```
|
||||||
@@ -67,11 +72,12 @@ The SHA-1 response must contain a 40-character hexadecimal SHA-1. Both of these
|
|||||||
|
|
||||||
### Bedrock Edition through GeyserMC
|
### Bedrock Edition through GeyserMC
|
||||||
|
|
||||||
- Set `bedrockResourcePack` to a local `.mcpack` or `.zip` file. `.mcpack` is fully supported.
|
- Preferred remote mode: set `bedrockResourcePackUrl` to a direct `.mcpack` or `.zip` download URL. RemotePackSync creates a Geyser `UrlPackCodec` and registers it through the public `SessionLoadResourcePacksEvent` API, so Geyser supplies the URL to connecting Bedrock clients.
|
||||||
- The source pack is checked asynchronously at `refreshInterval`. When its SHA-256 changes, RemotePackSync creates a managed copy under `plugins/RemotePackSync/bedrock-managed/`.
|
- The remote server must return `Content-Type: application/zip` and an exact `Content-Length`. Redirects are allowed only when they ultimately resolve to a valid direct download. The URL should be stable and should expose a changed ETag or URL when the pack changes.
|
||||||
- The managed copy receives a deterministic new header UUID derived from the updated source SHA-256. This makes Bedrock clients treat a changed pack as a new pack, while Geyser calculates and supplies the required final pack hash and size.
|
- Geyser validates/downloads remote URL packs during its lifecycle. Use `/remotepacksync reload` after changing the URL or after replacing a pack at the same URL; players already connected must reconnect.
|
||||||
- The original configured pack is never modified.
|
- Local alternative: set `bedrockResourcePack` to a `.mcpack` or `.zip` file. The source is checked asynchronously at `refreshInterval`; changes receive a deterministic manifest UUID and are managed under `plugins/RemotePackSync/bedrock-managed/`.
|
||||||
- Every new Geyser Bedrock connection receives the latest successfully processed pack through Geyser's public `SessionLoadResourcePacksEvent` API.
|
- The original configured local pack is never modified.
|
||||||
|
- Every new Geyser Bedrock connection receives the latest successfully registered pack.
|
||||||
- The pack must be a valid Bedrock pack containing `manifest.json` at the archive root. Geyser does **not** convert Java packs into Bedrock packs.
|
- The pack must be a valid Bedrock pack containing `manifest.json` at the archive root. Geyser does **not** convert Java packs into Bedrock packs.
|
||||||
- Bedrock's protocol only negotiates packs while a client connects. Players already connected when the file changes must reconnect to receive the new version; RemotePackSync cannot hot-push a replacement to an active Bedrock session.
|
- Bedrock's protocol only negotiates packs while a client connects. Players already connected when the file changes must reconnect to receive the new version; RemotePackSync cannot hot-push a replacement to an active Bedrock session.
|
||||||
- If Geyser is absent, Java Edition resource-pack operation continues normally and Bedrock delivery stays disabled.
|
- If Geyser is absent, Java Edition resource-pack operation continues normally and Bedrock delivery stays disabled.
|
||||||
@@ -83,7 +89,7 @@ The SHA-1 response must contain a 40-character hexadecimal SHA-1. Both of these
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
RemotePackSync compiles against `org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT` and Geyser API `2.11.0-SNAPSHOT`, targeting Java 25 as required by Minecraft/Spigot 26.2. Java packs are sent through Bukkit's stable `Player#setResourcePack(String, byte[])` API; Bedrock packs use Geyser's public pack/session API. Geyser is a soft dependency, so Java-only servers remain supported.
|
RemotePackSync compiles against `org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT` and Geyser API `2.11.0-SNAPSHOT`, targeting Java 25 as required by Minecraft/Spigot 26.2. Java packs are sent through Bukkit's stable `Player#setResourcePack(String, byte[])` API; Bedrock packs use Geyser's public `UrlPackCodec`/pack-session API. Geyser is a soft dependency, so Java-only servers remain supported.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,18 @@ public final class BedrockPackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subscribeToGeyser();
|
subscribeToGeyser();
|
||||||
refreshTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
|
if (newSettings.isRemote()) {
|
||||||
plugin, this::refreshPack, 0L, newSettings.refreshIntervalTicks());
|
// Geyser's URL codec validates the remote download and obtains its metadata.
|
||||||
plugin.getLogger().info("Bedrock resource-pack refresh enabled every "
|
// Geyser itself downloads remote packs once during its lifecycle, so do not
|
||||||
+ (newSettings.refreshIntervalTicks() / 20L) + " seconds.");
|
// repeatedly recreate the URL codec on a timer.
|
||||||
|
refreshTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, this::refreshPack);
|
||||||
|
plugin.getLogger().info("Bedrock remote resource-pack URL configured. Geyser will fetch it for new connections.");
|
||||||
|
} else {
|
||||||
|
refreshTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
|
||||||
|
plugin, this::refreshPack, 0L, newSettings.refreshIntervalTicks());
|
||||||
|
plugin.getLogger().info("Bedrock local resource-pack refresh enabled every "
|
||||||
|
+ (newSettings.refreshIntervalTicks() / 20L) + " seconds.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
@@ -111,6 +119,17 @@ public final class BedrockPackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (activeSettings.isRemote()) {
|
||||||
|
ResourcePack resourcePack = ResourcePack.create(PackCodec.url(activeSettings.remoteUrl()));
|
||||||
|
if (activeSettings != settings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentPack.set(new Snapshot(null, resourcePack));
|
||||||
|
plugin.getLogger().info("Bedrock remote resource-pack URL validated and registered: "
|
||||||
|
+ activeSettings.remoteUrl());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Path source = activeSettings.sourceFile();
|
Path source = activeSettings.sourceFile();
|
||||||
if (!Files.isRegularFile(source)) {
|
if (!Files.isRegularFile(source)) {
|
||||||
throw new IOException("Configured Bedrock pack does not exist or is not a regular file: " + source);
|
throw new IOException("Configured Bedrock pack does not exist or is not a regular file: " + source);
|
||||||
|
|||||||
@@ -6,30 +6,46 @@ import java.nio.file.Path;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/** Validated configuration for an optional local Bedrock resource pack. */
|
/** Validated configuration for an optional local or remote Bedrock resource pack. */
|
||||||
public record BedrockPackSettings(Path sourceFile, long refreshIntervalTicks) {
|
public record BedrockPackSettings(Path sourceFile, String remoteUrl, long refreshIntervalTicks) {
|
||||||
private static final long MINIMUM_REFRESH_SECONDS = 30L;
|
private static final long MINIMUM_REFRESH_SECONDS = 30L;
|
||||||
|
|
||||||
public static Optional<BedrockPackSettings> from(FileConfiguration config, Path dataDirectory) {
|
public static Optional<BedrockPackSettings> from(FileConfiguration config, Path dataDirectory) {
|
||||||
|
String configuredUrl = config.getString("bedrockResourcePackUrl", "").trim();
|
||||||
String configuredPath = config.getString("bedrockResourcePack", "").trim();
|
String configuredPath = config.getString("bedrockResourcePack", "").trim();
|
||||||
if (configuredPath.isEmpty()) {
|
if (!configuredUrl.isEmpty() && !configuredPath.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Configure only one of bedrockResourcePackUrl or bedrockResourcePack.");
|
||||||
|
}
|
||||||
|
if (configuredUrl.isEmpty() && configuredPath.isEmpty()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path source = Path.of(configuredPath);
|
Path source = null;
|
||||||
if (!source.isAbsolute()) {
|
if (!configuredPath.isEmpty()) {
|
||||||
source = dataDirectory.resolve(source);
|
source = Path.of(configuredPath);
|
||||||
|
if (!source.isAbsolute()) {
|
||||||
|
source = dataDirectory.resolve(source);
|
||||||
|
}
|
||||||
|
source = source.normalize();
|
||||||
|
String fileName = source.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||||
|
if (!fileName.endsWith(".mcpack") && !fileName.endsWith(".zip")) {
|
||||||
|
throw new IllegalArgumentException("bedrockResourcePack must reference a .mcpack or .zip file.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!configuredUrl.startsWith("https://") && !configuredUrl.startsWith("http://")) {
|
||||||
|
throw new IllegalArgumentException("bedrockResourcePackUrl must be an HTTP(S) direct download URL.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
source = source.normalize();
|
|
||||||
|
|
||||||
String fileName = source.getFileName().toString().toLowerCase(Locale.ROOT);
|
|
||||||
if (!fileName.endsWith(".mcpack") && !fileName.endsWith(".zip")) {
|
|
||||||
throw new IllegalArgumentException("bedrockResourcePack must reference a .mcpack or .zip file.");
|
|
||||||
}
|
|
||||||
long refreshSeconds = config.getLong("refreshInterval", 300L);
|
long refreshSeconds = config.getLong("refreshInterval", 300L);
|
||||||
if (refreshSeconds < MINIMUM_REFRESH_SECONDS) {
|
if (refreshSeconds < MINIMUM_REFRESH_SECONDS) {
|
||||||
throw new IllegalArgumentException("refreshInterval must be at least " + MINIMUM_REFRESH_SECONDS + " seconds.");
|
throw new IllegalArgumentException("refreshInterval must be at least " + MINIMUM_REFRESH_SECONDS + " seconds.");
|
||||||
}
|
}
|
||||||
return Optional.of(new BedrockPackSettings(source, Math.multiplyExact(refreshSeconds, 20L)));
|
return Optional.of(new BedrockPackSettings(source, configuredUrl.isEmpty() ? null : configuredUrl,
|
||||||
|
Math.multiplyExact(refreshSeconds, 20L)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRemote() {
|
||||||
|
return remoteUrl != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public final class RemotePackSyncPlugin extends JavaPlugin implements Listener,
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
getConfig().addDefault("bedrockResourcePackUrl", "");
|
||||||
getConfig().addDefault("bedrockResourcePack", "");
|
getConfig().addDefault("bedrockResourcePack", "");
|
||||||
getConfig().options().copyDefaults(true);
|
getConfig().options().copyDefaults(true);
|
||||||
saveConfig();
|
saveConfig();
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ sha1Url: ""
|
|||||||
# Seconds between remote SHA-1 and local Bedrock pack checks. Minimum: 30 seconds.
|
# Seconds between remote SHA-1 and local Bedrock pack checks. Minimum: 30 seconds.
|
||||||
refreshInterval: 300
|
refreshInterval: 300
|
||||||
|
|
||||||
# Optional local Bedrock Edition resource pack for players joining through GeyserMC.
|
# Optional remote Bedrock Edition resource-pack URL for players connecting through GeyserMC.
|
||||||
# The path may point to a .mcpack or .zip file. Relative paths are resolved from
|
# This must be a direct HTTP(S) download URL to a .mcpack or .zip pack. The server
|
||||||
# plugins/RemotePackSync/. Leave blank to disable Bedrock pack delivery.
|
# must return Content-Type: application/zip and an exact Content-Length.
|
||||||
|
# Geyser passes this URL to Bedrock clients using its URL pack codec.
|
||||||
|
bedrockResourcePackUrl: ""
|
||||||
|
|
||||||
|
# Optional local alternative. Configure this only when bedrockResourcePackUrl is blank.
|
||||||
|
# Relative paths are resolved from plugins/RemotePackSync/.
|
||||||
bedrockResourcePack: ""
|
bedrockResourcePack: ""
|
||||||
|
|||||||
Reference in New Issue
Block a user