1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2026-01-06 15:41:50 +00:00

Merge remote-tracking branch 'upstream/master' into feature/floodgate-merge

# Conflicts:
#	bootstrap/mod/fabric/build.gradle.kts
#	bootstrap/velocity/base/build.gradle.kts
#	core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java
This commit is contained in:
onebeastchris
2025-07-28 21:18:02 +02:00
39 changed files with 318 additions and 443 deletions

View File

@@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
## Supported Versions
Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.93 and Minecraft Java 1.21.7. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.94 and Minecraft Java 1.21.7 - 1.21.8. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.

View File

@@ -34,7 +34,7 @@ public interface MinecraftVersion {
/**
* Gets the Minecraft version as a String.
* Example: "1.20.2", or "1.20.40/1.20.41"
* Example formats: "1.21", "1.21.1", "1.21.22"
*
* @return the version string
*/

View File

@@ -6,8 +6,6 @@ plugins {
dependencies {
api(projects.core)
compileOnly(libs.netty.transport.native.io.uring)
implementation(libs.cloud.bungee)
implementation(libs.adventure.text.serializer.bungeecord)
compileOnlyApi(libs.bungeecord.proxy)
@@ -17,7 +15,6 @@ dependencies {
platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
platformRelocate("net.kyori")
platformRelocate("org.incendo")
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
@@ -31,16 +28,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
exclude(dependency("io.netty:netty-common:.*"))
exclude(dependency("io.netty:netty-buffer:.*"))
exclude(dependency("io.netty:netty-resolver:.*"))
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
exclude(dependency("io.netty.*:.*"))
}
}

View File

@@ -26,11 +26,9 @@
package org.geysermc.geyser.platform.bungeecord;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.IoEventLoop;
import io.netty.channel.IoEventLoopGroup;

View File

@@ -104,6 +104,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
geyserLogger.warning("Unable to check the versions supported by this proxy! " + e.getMessage());
}
// See https://github.com/SpigotMC/BungeeCord/blob/e62fc6c2916a991e00177c580986d8b1a22fdb41/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java#L138
if (Boolean.getBoolean("bungee.io_uring")) {
System.setProperty("Mcpl.io_uring", "true");
}
if (!this.loadConfig()) {
return;
}

View File

@@ -15,25 +15,23 @@ dependencies {
modApi(libs.fabric.api)
api(project(":mod", configuration = "namedElements"))
shadow(project(path = ":mod", configuration = "transformProductionFabric")) {
isTransitive = false
}
shadow(projects.core) { isTransitive = false }
shadowBundle(project(path = ":mod", configuration = "transformProductionFabric"))
shadowBundle(projects.core)
includeTransitive(projects.core)
// These are NOT transitively included, and instead shadowed + relocated.
// Avoids fabric complaining about non-SemVer versioning
shadow(libs.protocol.connection) { isTransitive = false }
shadow(libs.protocol.common) { isTransitive = false }
shadow(libs.protocol.codec) { isTransitive = false }
shadow(libs.raknet) { isTransitive = false }
shadow(libs.mcprotocollib) { isTransitive = false }
shadowBundle(libs.protocol.connection)
shadowBundle(libs.protocol.common)
shadowBundle(libs.protocol.codec)
shadowBundle(libs.raknet)
shadowBundle(libs.mcprotocollib)
// Since we also relocate cloudburst protocol: shade erosion common
shadow(libs.erosion.common) { isTransitive = false }
shadowBundle(libs.erosion.common)
// Let's shade in our own api/common module
shadow(projects.api) { isTransitive = false }
// Let's shade in our own api module
shadowBundle(projects.api)
modImplementation(libs.cloud.fabric)
include(libs.cloud.fabric)

View File

@@ -29,19 +29,17 @@ dependencies {
neoForge(libs.neoforge.minecraft)
api(project(":mod", configuration = "namedElements"))
shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) {
isTransitive = false
}
shadow(projects.core) { isTransitive = false }
shadowBundle(project(path = ":mod", configuration = "transformProductionNeoForge"))
shadowBundle(projects.core)
// Minecraft (1.21.2+) includes jackson. But an old version!
shadow(libs.jackson.core) { isTransitive = false }
shadow(libs.jackson.databind) { isTransitive = false }
shadow(libs.jackson.dataformat.yaml) { isTransitive = false }
shadow(libs.jackson.annotations) { isTransitive = false }
shadowBundle(libs.jackson.core)
shadowBundle(libs.jackson.databind)
shadowBundle(libs.jackson.dataformat.yaml)
shadowBundle(libs.jackson.annotations)
// Let's shade in our own api
shadow(projects.api) { isTransitive = false }
shadowBundle(projects.api)
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)

View File

@@ -45,8 +45,6 @@ platformRelocate("me.lucko.commodore")
platformRelocate("org.incendo")
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.viaversion)
provided("com.mojang", "authlib")
@@ -62,23 +60,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
// We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
exclude(dependency("io.netty:netty-common:.*"))
exclude(dependency("io.netty:netty-buffer:.*"))
exclude(dependency("io.netty:netty-resolver:.*"))
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
exclude("io.netty", libs.netty.codec.haproxy)
// Commodore includes Brigadier
exclude(dependency("com.mojang:.*"))
@@ -88,7 +71,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
gameVersions.addAll("1.16.5", "1.17", "1.17.1", "1.18", "1.18.1", "1.18.2", "1.19",
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6")
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.21.6")
loaders.addAll("spigot", "paper")
}

View File

@@ -15,7 +15,6 @@ dependencies {
}
implementation(libs.bundles.jline)
implementation(libs.bundles.log4j)
}
@@ -33,11 +32,6 @@ tasks.named<Jar>("jar") {
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
archiveBaseName.set("Geyser-Standalone")
// temporary measure - incubator's io_uring is not compatible with 4.2.1
dependencies {
exclude(dependency("io.netty.incubator:.*"))
}
transform(Log4j2PluginsCacheFileTransformer())
}

View File

@@ -19,31 +19,6 @@ platformRelocate("org.yaml")
platformRelocate("org.bstats") //todo
platformRelocate("org.incendo")
platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
exclude("com.google.*:*")
// Needed because Velocity provides every dependency except netty-resolver-dns
exclude("io.netty.incubator:.*")
exclude("io.netty:netty-transport-native-epoll:*")
exclude("io.netty:netty-transport-native-unix-common:*")
exclude("io.netty:netty-transport-native-kqueue:*")
exclude("io.netty:netty-handler:*")
exclude("io.netty:netty-common:*")
exclude("io.netty:netty-buffer:*")
exclude("io.netty:netty-resolver:*")
exclude("io.netty:netty-transport:*")
exclude("io.netty:netty-codec:*")
exclude("io.netty:netty-codec-haproxy:*")
exclude("org.slf4j:*")
exclude("org.ow2.asm:*")
// Exclude all Kyori dependencies except the legacy NBT serializer
exclude("net.kyori:adventure-api:*")
exclude("net.kyori:examination-api:*")
exclude("net.kyori:examination-string:*")
exclude("net.kyori:adventure-text-serializer-gson:*")
exclude("net.kyori:adventure-text-serializer-legacy:*")
exclude("net.kyori:adventure-nbt:*")
// These dependencies are already present on the platform
provided(libs.velocity.api)
@@ -53,27 +28,11 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
// Needed because Velocity provides every dependency except netty-resolver-dns
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
exclude(dependency("io.netty:netty-common:.*"))
exclude(dependency("io.netty:netty-buffer:.*"))
exclude(dependency("io.netty:netty-resolver:.*"))
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-haproxy:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:.*"))
exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*"))
// Exclude all Kyori dependencies except the legacy NBT serializer
exclude(dependency("net.kyori:adventure-api:.*"))
exclude(dependency("net.kyori:examination-api:.*"))
exclude(dependency("net.kyori:examination-string:.*"))
exclude(dependency("net.kyori:adventure-text-serializer-gson:.*"))
exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*"))
exclude(dependency("net.kyori:adventure-nbt:.*"))
// Exclude all Kyori dependencies
exclude(dependency("net.kyori:.*:.*"))
}
}

View File

@@ -111,6 +111,11 @@ public class GeyserVelocityPlatform implements GeyserBootstrap, IsolatedPlatform
geyserLogger.error("/_____________\\");
}
// Only use io_uring when velocity does as well
if (Boolean.getBoolean("velocity.enable-iouring-transport")) {
System.setProperty("Mcpl.io_uring", "true");
}
if (!loadConfig()) {
return;
}

View File

@@ -28,7 +28,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
dependencies {
exclude(dependency("com.google.*:.*"))
exclude(dependency("io.netty:.*"))
exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*"))
}

View File

@@ -42,9 +42,12 @@ fun Project.relocate(pattern: String) {
}
}
fun Project.exclude(group: String) {
// Excludes all dependencies from a module - except one
fun Project.exclude(group: String, lib: Provider<MinimalExternalModuleDependency>) {
tasks.named<ShadowJar>("shadowJar") {
exclude(group)
dependencies {
exclude { it.moduleGroup == group && it.moduleName != lib.get().module.name }
}
}
}
@@ -58,10 +61,6 @@ fun Project.platformRelocate(pattern: String, exclusion: String = "") {
val providedDependencies = mutableMapOf<String, MutableSet<String>>()
fun getProvidedDependenciesForProject(projectName: String): MutableSet<String> {
return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet()
}
fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) {
providedDependencies.getOrPut(project.name) { mutableSetOf() }
.add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}")

View File

@@ -41,11 +41,6 @@ repositories {
mavenContent { snapshotsOnly() }
}
// BungeeCord
maven("https://oss.sonatype.org/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
}
// NeoForge
maven("https://maven.neoforged.net/releases") {
mavenContent { releasesOnly() }
@@ -62,9 +57,6 @@ repositories {
name = "viaversion"
}
// For Adventure snapshots
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
// Jitpack for e.g. MCPL
maven("https://jitpack.io") {
content { includeGroupByRegex("com\\.github\\..*") }

View File

@@ -24,8 +24,8 @@ provided("io.netty", "netty-transport-native-epoll")
provided("io.netty", "netty-transport-native-unix-common")
provided("io.netty", "netty-transport-classes-kqueue")
provided("io.netty", "netty-transport-native-kqueue")
provided("io.netty.incubator", "netty-incubator-transport-native-io_uring")
provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring")
provided("io.netty", "netty-transport-native-io_uring")
provided("io.netty", "netty-transport-classes-io_uring")
provided("io.netty", "netty-handler")
provided("io.netty", "netty-common")
provided("io.netty", "netty-buffer")
@@ -33,9 +33,6 @@ provided("io.netty", "netty-resolver")
provided("io.netty", "netty-transport")
provided("io.netty", "netty-codec")
provided("io.netty", "netty-codec-base")
provided("io.netty", "netty-resolver-dns")
provided("io.netty", "netty-resolver-dns-native-macos")
provided("io.netty", "netty-resolver-dns-classes-macos")
provided("org.ow2.asm", "asm")
// cloud-fabric/cloud-neoforge jij's all cloud depends already
@@ -58,6 +55,11 @@ indra {
configurations {
create("includeTransitive").isTransitive = true
create("shadowBundle") {
isCanBeResolved = true
isCanBeConsumed = false
isTransitive = false
}
}
tasks {
@@ -71,7 +73,7 @@ tasks {
shadowJar {
// Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be
configurations = listOf(project.configurations.shadow.get())
configurations = listOf(project.configurations.getByName("shadowBundle"))
// The remapped shadowJar is the final desired mod jar
archiveVersion.set(project.version.toString())
archiveClassifier.set("shaded")
@@ -93,18 +95,16 @@ tasks {
}
afterEvaluate {
val providedDependencies = getProvidedDependenciesForProject(project.name)
// These are shaded, no need to JiJ them
configurations["shadow"].dependencies.forEach {shadowed ->
//println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
providedDependencies.add("${shadowed.group}:${shadowed.name}")
}
val providedDependencies = providedDependencies[project.name]!!
val shadedDependencies = configurations.getByName("shadowBundle")
.dependencies.stream().map { dependency -> "${dependency.group}:${dependency.name}" }.toList()
// Now: Include all transitive dependencies that aren't excluded
configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep ->
if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}")
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) {
val name = "${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}"
if (!shadedDependencies.contains(name) and !providedDependencies.contains(name)
and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")
) {
println("Including dependency via JiJ: ${dep.id}")
dependencies.add("include", dep.moduleVersion.id.toString())
} else {

View File

@@ -13,7 +13,7 @@ modrinth {
versionNumber.set(projectVersion(project))
versionType.set("beta")
changelog.set(System.getenv("CHANGELOG") ?: "")
gameVersions.add(libs.minecraft.get().version as String)
gameVersions.addAll("1.21.7", libs.minecraft.get().version as String)
failSilently.set(true)
syncBodyFrom.set(rootProject.file("README.md").readText())

View File

@@ -37,18 +37,17 @@ dependencies {
exclude("io.netty", "*")
}
implementation(libs.netty.resolver.dns)
implementation(libs.netty.resolver.dns.native.macos) { artifact { classifier = "osx-x86_64" } }
implementation(libs.netty.codec.haproxy)
// Network dependencies we are updating ourselves
api(libs.netty.handler)
implementation(libs.netty.codec.haproxy)
api(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
// kqueue is macos only
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
//api(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
//implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
api(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
// Adventure text serialization
api(libs.bundles.adventure)

View File

@@ -42,7 +42,6 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils;
import org.geysermc.api.Geyser;
import org.geysermc.cumulus.form.Form;
@@ -110,7 +109,6 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -796,11 +794,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
@Override
public @NonNull List<MinecraftVersion> supportedBedrockVersions() {
ArrayList<MinecraftVersion> versions = new ArrayList<>();
for (BedrockCodec codec : GameProtocol.SUPPORTED_BEDROCK_CODECS) {
versions.add(new MinecraftVersionImpl(codec.getMinecraftVersion(), codec.getProtocolVersion()));
}
return Collections.unmodifiableList(versions);
return Collections.unmodifiableList(GameProtocol.SUPPORTED_BEDROCK_VERSIONS);
}
@Override

View File

@@ -26,8 +26,8 @@
package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.databind.JsonNode;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
@@ -43,6 +43,25 @@ import java.util.List;
public class VersionCommand extends GeyserCommand {
private static final String SUPPORTED_BEDROCK_RANGE;
private static final String SUPPORTED_JAVA_RANGE;
static {
List<MinecraftVersion> bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_VERSIONS;
if (bedrockVersions.size() > 1) {
SUPPORTED_BEDROCK_RANGE = bedrockVersions.get(0).versionString() + " - " + bedrockVersions.get(bedrockVersions.size() - 1).versionString();
} else {
SUPPORTED_BEDROCK_RANGE = bedrockVersions.get(0).versionString();
}
List<String> javaVersions = GameProtocol.getJavaVersions();
if (javaVersions.size() > 1) {
SUPPORTED_JAVA_RANGE = javaVersions.get(0) + " - " + javaVersions.get(javaVersions.size() - 1);
} else {
SUPPORTED_JAVA_RANGE = javaVersions.get(0);
}
}
private final GeyserImpl geyser;
public VersionCommand(GeyserImpl geyser, String name, String description, String permission) {
@@ -54,23 +73,8 @@ public class VersionCommand extends GeyserCommand {
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
String bedrockVersions;
List<BedrockCodec> supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS;
if (supportedCodecs.size() > 1) {
bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion();
} else {
bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.get(0).getMinecraftVersion();
}
String javaVersions;
List<String> supportedJavaVersions = GameProtocol.getJavaVersions();
if (supportedJavaVersions.size() > 1) {
javaVersions = supportedJavaVersions.get(0) + " - " + supportedJavaVersions.get(supportedJavaVersions.size() - 1);
} else {
javaVersions = supportedJavaVersions.get(0);
}
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(),
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
GeyserImpl.NAME, GeyserImpl.VERSION, SUPPORTED_JAVA_RANGE, SUPPORTED_BEDROCK_RANGE));
// Disable update checking in dev mode and for players in Geyser Standalone
if (!GeyserImpl.getInstance().isProductionEnvironment() || (!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {

View File

@@ -34,11 +34,11 @@ import com.google.common.io.Files;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.api.util.BedrockPlatform;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
@@ -223,9 +223,9 @@ public class DumpInfo {
private final int javaProtocol;
MCInfo() {
this.bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockCodec::getMinecraftVersion).toList();
this.bedrockProtocols = GameProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockCodec::getProtocolVersion).toList();
this.defaultBedrockProtocol = GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion();
this.bedrockVersions = GameProtocol.SUPPORTED_BEDROCK_VERSIONS.stream().map(MinecraftVersion::versionString).toList();
this.bedrockProtocols = GameProtocol.SUPPORTED_BEDROCK_PROTOCOLS;
this.defaultBedrockProtocol = GameProtocol.DEFAULT_BEDROCK_PROTOCOL;
this.javaVersions = GameProtocol.getJavaVersions();
this.javaProtocol = GameProtocol.getJavaProtocolVersion();
}

View File

@@ -1191,6 +1191,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.INT, WolfEntity::setCollarColor)
.addTranslator(MetadataTypes.INT, WolfEntity::setWolfAngerTime)
.addTranslator(MetadataTypes.WOLF_VARIANT, WolfEntity::setVariant)
.addTranslator(null) // sound variant; these aren't clientsided anyways... right??
.build();
// As of 1.18 these don't track entity data at all

View File

@@ -75,10 +75,11 @@ public class FireworkEntity extends Entity {
if (optional.isPresent() && optional.getAsInt() == session.getPlayerEntity().getEntityId()) {
// If we don't send this, the bedrock client will always stop boosting after 20 ticks
// However this is not the case for Java as the player will stop boosting after entity despawn.
// So we let player boost "infinitely" and then only stop them when the entity despawn.
// So we let player boost for a really long time and then only stop them when the entity despawn.
// Also doing this allow player to boost simply by having a fireworks rocket attached to them
// and not necessary have to use a rocket (as some plugin do this to boost player)
sendElytraBoost(Integer.MAX_VALUE);
// You can't really send Integer.MAX_VALUE since Bedrock client doesn't seem to like way too large number very much (as of 1.21.73).
sendElytraBoost(1000000);
this.attachedToSession = true;
// We need to keep track of the fireworks rockets.
@@ -99,7 +100,7 @@ public class FireworkEntity extends Entity {
// Else player will stop boosting even if the fireworks is not attached to them or there is a fireworks that is boosting them
// and not just this one.
if (this.attachedToSession && session.getAttachedFireworkRockets().isEmpty()) {
// Since we send an effect packet for player to boost "infinitely", we have to stop them when the entity despawn.
// Since we send an effect packet for player to boost really long, we have to stop them when the entity despawn.
sendElytraBoost(0);
this.attachedToSession = false;
}
@@ -112,6 +113,7 @@ public class FireworkEntity extends Entity {
movementEffect.setDuration(duration);
movementEffect.setEffectType(MovementEffectType.GLIDE_BOOST);
movementEffect.setEntityRuntimeId(session.getPlayerEntity().getGeyserId());
movementEffect.setTick(session.getClientTicks());
session.sendUpstreamPacket(movementEffect);
}
}

View File

@@ -109,6 +109,12 @@ public class SessionPlayerEntity extends PlayerEntity {
@Getter @Setter
private Vector3f lastTickEndVelocity = Vector3f.ZERO;
/**
* The client claimed interact rotation, intended for touch (pocket) user.
*/
@Getter @Setter
private Vector2f bedrockInteractRotation = Vector2f.ZERO;
/**
* Determines if our position is currently out-of-sync with the Java server
* due to our workaround for the void floor

View File

@@ -25,6 +25,8 @@
package org.geysermc.geyser.network;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
@@ -32,13 +34,15 @@ import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.impl.MinecraftVersionImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
import org.geysermc.mcprotocollib.protocol.codec.PacketCodec;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
/**
* Contains information about the supported protocols in Geyser.
@@ -46,17 +50,30 @@ import java.util.StringJoiner;
public final class GameProtocol {
/**
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
* All Bedrock protocol codecs that Geyser uses
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v819.CODEC.toBuilder()
.minecraftVersion("1.21.93")
.build());
private static final List<BedrockCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
/**
* A list of all supported Bedrock versions that can join Geyser
* All bedrock protocol versions that Geyser supports
*/
public static final List<BedrockCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
public static final IntList SUPPORTED_BEDROCK_PROTOCOLS = new IntArrayList();
/**
* All bedrock minecraft versions that Geyser supports.
* There may be multiple MinecraftVersions with the same protocol version.
*/
public static final List<MinecraftVersion> SUPPORTED_BEDROCK_VERSIONS = new ArrayList<>();
/**
* The latest Bedrock protocol version that Geyser supports.
*/
public static final int DEFAULT_BEDROCK_PROTOCOL;
/**
* The latest Bedrock Minecraft version that Geyser supports.
*/
public static final String DEFAULT_BEDROCK_VERSION;
/**
* Java codec that is supported. We only ever support one version for
@@ -65,16 +82,44 @@ public final class GameProtocol {
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
static {
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v786.CODEC.toBuilder()
.minecraftVersion("1.21.70 - 1.21.73")
.build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v800.CODEC.toBuilder()
.minecraftVersion("1.21.80 - 1.21.84")
.build()));
SUPPORTED_BEDROCK_CODECS.add((CodecProcessor.processCodec(Bedrock_v818.CODEC.toBuilder()
.minecraftVersion("1.21.90 - 1.21.92")
.build())));
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
// Strict ordering
register(Bedrock_v786.CODEC, "1.21.70", "1.21.71", "1.21.72", "1.21.73");
register(Bedrock_v800.CODEC, "1.21.80", "1.21.81", "1.21.82", "1.21.83", "1.21.84");
register(Bedrock_v818.CODEC, "1.21.90", "1.21.91", "1.21.92");
register(Bedrock_v819.CODEC, "1.21.93", "1.21.94");
MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1);
DEFAULT_BEDROCK_VERSION = latestBedrock.versionString();
DEFAULT_BEDROCK_PROTOCOL = latestBedrock.protocolVersion();
}
/**
* Registers a bedrock codec, along with its protocol version and minecraft version(s).
* This method must be called in ascending order in terms of protocol version.
*
* @param codec the codec to register
* @param minecraftVersions all versions the codec supports, in ascending order
*/
private static void register(BedrockCodec codec, String... minecraftVersions) {
// modify packet serializers to better fit our use
codec = CodecProcessor.processCodec(codec);
SUPPORTED_BEDROCK_CODECS.add(codec);
SUPPORTED_BEDROCK_PROTOCOLS.add(codec.getProtocolVersion());
for (String version : minecraftVersions) {
SUPPORTED_BEDROCK_VERSIONS.add(new MinecraftVersionImpl(version, codec.getProtocolVersion()));
}
}
/**
* Registers a bedrock codec, its protocol version, and a single minecraft version which is taken from the codec.
* This method must be called in ascending order in terms of protocol version.
*
* @param codec the codec to register
*/
private static void register(BedrockCodec codec) {
register(codec, codec.getMinecraftVersion());
}
/**
@@ -109,22 +154,13 @@ public final class GameProtocol {
return session.protocolVersion() == Bedrock_v800.CODEC.getProtocolVersion();
}
/**
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
*
* @return the packet codec for Minecraft: Java Edition
*/
public static PacketCodec getJavaCodec() {
return DEFAULT_JAVA_CODEC;
}
/**
* Gets the supported Minecraft: Java Edition version names.
*
* @return the supported Minecraft: Java Edition version names
*/
public static List<String> getJavaVersions() {
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion());
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion(), "1.21.8");
}
/**
@@ -142,31 +178,23 @@ public final class GameProtocol {
* @return the supported Minecraft: Java Edition version
*/
public static String getJavaMinecraftVersion() {
return DEFAULT_JAVA_CODEC.getMinecraftVersion();
return "1.21.8";
}
/**
* @return a string showing all supported Bedrock versions for this Geyser instance
*/
public static String getAllSupportedBedrockVersions() {
StringJoiner joiner = new StringJoiner(", ");
for (BedrockCodec packetCodec : SUPPORTED_BEDROCK_CODECS) {
joiner.add(packetCodec.getMinecraftVersion());
}
return joiner.toString();
return SUPPORTED_BEDROCK_VERSIONS.stream()
.map(MinecraftVersion::versionString)
.collect(Collectors.joining(", "));
}
/**
* @return a string showing all supported Java versions for this Geyser instance
*/
public static String getAllSupportedJavaVersions() {
StringJoiner joiner = new StringJoiner(", ");
for (String version : getJavaVersions()) {
joiner.add(version);
}
return joiner.toString();
return String.join(", ", getJavaVersions());
}
private GameProtocol() {

View File

@@ -28,6 +28,7 @@ package org.geysermc.geyser.network;
import io.netty.channel.Channel;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
@@ -41,16 +42,13 @@ import java.net.InetSocketAddress;
public class GeyserServerInitializer extends BedrockServerInitializer {
private final GeyserImpl geyser;
// There is a constructor that doesn't require inputting threads, but older Netty versions don't have it
@Getter
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser player thread"));
public GeyserServerInitializer(GeyserImpl geyser) {
this.geyser = geyser;
}
public DefaultEventLoopGroup getEventLoopGroup() {
return eventLoopGroup;
}
@Override
public void initSession(@NonNull BedrockServerSession bedrockServerSession) {
try {

View File

@@ -60,11 +60,11 @@ import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.geysermc.geyser.api.pack.UrlPackCodec;
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.pack.ResourcePackHolder;
import org.geysermc.geyser.pack.url.GeyserUrlPackCodec;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
@@ -120,8 +120,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
private boolean setCorrectCodec(int protocolVersion) {
BedrockCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
if (packetCodec == null) {
// None of our Bedrock codecs support this client version, so we can simply compare it to our default protocol.
String supportedVersions = GameProtocol.getAllSupportedBedrockVersions();
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_PROTOCOL) {
// Too early to determine session locale
String disconnectMessage = GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions);
// If the latest release matches this version, then let the user know.
@@ -132,7 +133,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
}
session.disconnect(disconnectMessage);
return false;
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_PROTOCOL) {
// A note on the following line: various older client versions have different forms of DisconnectPacket.
// Using only the latest BedrockCompat for such clients leads to inaccurate disconnect messages: https://github.com/GeyserMC/Geyser/issues/4378
// This updates the BedrockCompat protocol if necessary:
@@ -344,21 +345,18 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return;
}
ResourcePack pack = holder.pack();
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
PackCodec codec = pack.codec();
PackCodec codec = holder.codec();
// If a remote pack ends up here, that usually implies that a client was not able to download the pack
if (codec instanceof UrlPackCodec urlPackCodec) {
ResourcePackLoader.testRemotePack(session, urlPackCodec, packet.getPackId(), packet.getPackVersion());
if (!resourcePackLoadEvent.value(pack.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
if (codec instanceof GeyserUrlPackCodec urlPackCodec) {
ResourcePackLoader.testRemotePack(session, urlPackCodec, holder);
if (!resourcePackLoadEvent.value(holder.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
session.disconnect("Unable to provide downloaded resource pack. Contact an administrator!");
currentlySendingChunks = false;
return;
}
}
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
data.setChunkIndex(packet.getChunkIndex());
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
data.setPackVersion(packet.getPackVersion());

View File

@@ -30,14 +30,10 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.uring.IoUring;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.Future;
import lombok.Getter;
import net.jodah.expiringmap.ExpirationPolicy;
@@ -64,13 +60,13 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.mcprotocollib.network.helper.TransportHelper;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT;
@@ -82,7 +78,7 @@ public final class GeyserServer {
/*
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
*/
private static final String PING_VERSION = pingVersion();
private static final String PING_VERSION = GameProtocol.DEFAULT_BEDROCK_VERSION;
private static final int PING_VERSION_BYTES_LENGTH = PING_VERSION.getBytes(StandardCharsets.UTF_8).length;
private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length;
/**
@@ -90,7 +86,8 @@ public final class GeyserServer {
*/
private static final int MAGIC_RAKNET_LENGTH = 338;
private static final Transport TRANSPORT = compatibleTransport();
// Let MCPL determine the transport type -> less code duplication and risk of ending up with 2 different types
private static final TransportHelper.TransportType TRANSPORT = TransportHelper.TRANSPORT_TYPE;
/**
* See {@link EventLoopGroup#shutdownGracefully(long, long, TimeUnit)}
@@ -124,8 +121,8 @@ public final class GeyserServer {
this.geyser = geyser;
this.listenCount = Bootstraps.isReusePortAvailable() ? Integer.getInteger("Geyser.ListenCount", 2) : 1;
GeyserImpl.getInstance().getLogger().debug("Listen thread count: " + listenCount);
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount);
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount, new DefaultThreadFactory("GeyserServer", true));
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount, new DefaultThreadFactory("GeyserServerChild", true));
this.bootstrap = this.createBootstrap();
// setup SO_REUSEPORT if exists - or, if the option does not actually exist, reset listen count
@@ -201,16 +198,18 @@ public final class GeyserServer {
}
}
@SuppressWarnings("Convert2MethodRef")
private ServerBootstrap createBootstrap() {
if (this.geyser.getConfig().isDebugMode()) {
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
this.geyser.getLogger().debug("Transport type: " + TRANSPORT.method().name());
if (TRANSPORT.datagramChannelClass() == NioDatagramChannel.class) {
if (System.getProperties().contains("disableNativeEventLoop")) {
this.geyser.getLogger().debug("EventLoop type is NIO because native event loops are disabled.");
} else {
// Use lambda here, not method reference, or else NoClassDefFoundError for Epoll/KQueue will not be caught
this.geyser.getLogger().debug("Reason for no Epoll: " + throwableOrCaught(() -> Epoll.unavailabilityCause()));
this.geyser.getLogger().debug("Reason for no KQueue: " + throwableOrCaught(() -> KQueue.unavailabilityCause()));
this.geyser.getLogger().debug("Reason for no IoUring: " + throwableOrCaught(() -> IoUring.unavailabilityCause()));
}
}
}
@@ -229,7 +228,7 @@ public final class GeyserServer {
this.geyser.getLogger().debug("Setting RakNet send cookie to " + rakSendCookie);
return new ServerBootstrap()
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannelClass()))
.group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true)
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
@@ -312,7 +311,7 @@ public final class GeyserServer {
.edition("MCPE")
.gameType("Survival") // Can only be Survival or Creative as of 1.16.210.59
.nintendoLimited(false)
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_PROTOCOL)
.version(PING_VERSION)
.ipv4Port(this.broadcastPort)
.ipv6Port(this.broadcastPort)
@@ -387,17 +386,6 @@ public final class GeyserServer {
return pong;
}
private static String pingVersion() {
// BedrockPong version is required to not be empty as of 1.16.210.59.
// Can only contain . and numbers, so use the latest version instead of sending all
var version = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
var versionSplit = version.split("/");
if (versionSplit.length > 1) {
version = versionSplit[versionSplit.length - 1];
}
return version;
}
/**
* @return the throwable from the given supplier, or the throwable caught while calling the supplier.
*/
@@ -429,38 +417,4 @@ public final class GeyserServer {
return defaultValue;
}
}
private static Transport compatibleTransport() {
// FIXME supporting io_uring post 4.2 requires more changes
// if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
// && IOUring.isAvailable()
// && Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
// }
if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) {
return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new);
}
if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) {
return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new);
}
return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new);
}
private record Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) {
}
/**
* Used so implementations can opt to remove these dependencies if so desired
*/
private static boolean isClassAvailable(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -57,7 +57,7 @@ import java.util.concurrent.TimeUnit;
* Manages a Minecraft Java session over our LocalChannel implementations.
*/
public final class LocalSession extends ClientNetworkSession {
private static DefaultEventLoopGroup DEFAULT_EVENT_LOOP_GROUP;
private static EventLoopGroup DEFAULT_EVENT_LOOP_GROUP;
private static PreferredDirectByteBufAllocator PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR = null;
private final SocketAddress spoofedRemoteAddress;

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.pack;
import lombok.With;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
@@ -32,6 +33,7 @@ import org.geysermc.geyser.api.pack.ResourcePackManifest;
import java.util.Objects;
@With
public record GeyserResourcePack(
@NonNull PackCodec codec,
@NonNull ResourcePackManifest manifest,

View File

@@ -25,11 +25,17 @@
package org.geysermc.geyser.pack;
import lombok.With;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.pack.PackCodec;
import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.api.pack.ResourcePackManifest;
import org.geysermc.geyser.api.pack.option.PriorityOption;
import org.geysermc.geyser.pack.option.OptionHolder;
import java.util.UUID;
@With
public record ResourcePackHolder(
@NonNull GeyserResourcePack pack,
@NonNull OptionHolder optionHolder
@@ -42,4 +48,16 @@ public record ResourcePackHolder(
public ResourcePack resourcePack() {
return this.pack;
}
public PackCodec codec() {
return this.pack.codec();
}
public UUID uuid() {
return this.pack.uuid();
}
public ResourcePackManifest.Version version() {
return pack.manifest().header().version();
}
}

View File

@@ -27,10 +27,14 @@ package org.geysermc.geyser.pack.url;
import lombok.Getter;
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.UrlPackCodec;
import org.geysermc.geyser.pack.GeyserResourcePack;
import org.geysermc.geyser.pack.ResourcePackHolder;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
import org.geysermc.geyser.text.GeyserLocale;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
@@ -46,6 +50,12 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
this.url = url;
}
private GeyserUrlPackCodec(@NonNull String url, PathPackCodec fallback) {
Objects.requireNonNull(url);
this.fallback = fallback;
this.url = url;
}
@Override
public byte @NonNull [] sha256() {
Objects.requireNonNull(fallback, "must call #create() before attempting to get the sha256!");
@@ -87,4 +97,43 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
public @NonNull String url() {
return this.url;
}
/**
* Tests whether Geyser's "mirror" of the remote pack needs to be updated.
* This is triggered if a Bedrock client is unable to download a pack
* @param holder the current resource pack holder with the "originally" known manifest
*/
public void testForChanges(ResourcePackHolder holder) {
ResourcePackLoader.downloadPack(url, true)
.thenAccept(backingPathCodec -> {
GeyserResourcePack updatedPack = ResourcePackLoader.readPack(backingPathCodec.path())
.contentKey(holder.pack().contentKey())
.build();
if (updatedPack.uuid().equals(holder.uuid())) {
var currentVersion = holder.version().toString();
var updatedVersion = updatedPack.manifest().header().version().toString();
if (currentVersion.equals(updatedVersion)) {
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
return;
} else {
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
.formatted(currentVersion, updatedVersion, url));
}
} else {
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
}
// Update to new url pack codec (same url, updated fallback), and keep content key
GeyserResourcePack pack = updatedPack.withCodec(new GeyserUrlPackCodec(url, backingPathCodec));
// Keep the pack options that were previously set
Registries.RESOURCE_PACKS.get().put(updatedPack.uuid(), holder.withPack(pack));
})
.exceptionally(throwable -> {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), throwable);
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
return null;
});
}
}

View File

@@ -62,7 +62,6 @@ import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@@ -80,7 +79,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
* If a client rejects such a pack, it falls back to the old method, and Geyser serves a cached variant.
*/
private static final Cache<String, UrlPackCodec> CACHED_FAILED_PACKS = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.expireAfterWrite(15, TimeUnit.MINUTES)
.build();
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
@@ -268,77 +267,28 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
*
* @param codec the codec of the resource pack that wasn't successfully downloaded by a Bedrock client.
*/
public static void testRemotePack(GeyserSession session, UrlPackCodec codec, UUID packId, String packVersion) {
public static void testRemotePack(GeyserSession session, GeyserUrlPackCodec codec, ResourcePackHolder holder) {
if (CACHED_FAILED_PACKS.getIfPresent(codec.url()) == null) {
String url = codec.url();
CACHED_FAILED_PACKS.put(url, codec);
CACHED_FAILED_PACKS.put(codec.url(), codec);
GeyserImpl.getInstance().getLogger().warning(
"Bedrock client (%s, playing on %s) was not able to download the resource pack at %s. Checking for changes now:"
"Bedrock client (%s, playing on %s) was not able to download the resource pack at %s!"
.formatted(session.bedrockUsername(), session.getClientData().getDeviceOs().name(), codec.url())
);
downloadPack(codec.url(), true).whenComplete((pathPackCodec, e) -> {
if (e != null) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
if (GeyserImpl.getInstance().getLogger().isDebug()) {
e.printStackTrace();
if (pathPackCodec != null) {
deleteFile(pathPackCodec.path());
}
return;
}
}
GeyserResourcePack newPack = readPack(pathPackCodec.path()).build();
if (newPack.uuid().equals(packId)) {
if (packVersion.equals(newPack.manifest().header().version().toString())) {
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
} else {
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
.formatted(packVersion, newPack.manifest().header().version().toString(), url));
}
} else {
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
}
// This should be safe to do as we're not directly using registries to read packs.
// Instead, they're cached per-session in the SessionLoadResourcePacks event
Registries.RESOURCE_PACKS.get().remove(packId);
Registries.RESOURCE_PACKS.get().put(newPack.uuid(), ResourcePackHolder.of(newPack));
if (codec instanceof GeyserUrlPackCodec geyserUrlPackCodec
&& geyserUrlPackCodec.getFallback() != null) {
Path path = geyserUrlPackCodec.getFallback().path();
try {
GeyserImpl.getInstance().getScheduledThread().schedule(() -> {
CACHED_FAILED_PACKS.invalidate(codec.url());
deleteFile(path);
}, 5, TimeUnit.MINUTES);
} catch (RejectedExecutionException exception) {
// No scheduling here, probably because we're shutting down?
deleteFile(path);
}
}
});
}
}
private static void deleteFile(Path path) {
if (path.toFile().exists()) {
try {
Files.delete(path);
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Unable to delete old pack! " + e.getMessage());
e.printStackTrace();
if (!Registries.RESOURCE_PACKS.get().containsKey(holder.uuid())) {
GeyserImpl.getInstance().getLogger().warning("Skipping remote resource pack check as pack is not present in global resource pack registry.");
return;
}
codec.testForChanges(holder);
}
}
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean force) throws IllegalArgumentException {
return CompletableFuture.supplyAsync(() -> {
Path path;
try {
path = WebUtils.downloadRemotePack(url, testing);
path = WebUtils.downloadRemotePack(url, force);
} catch (Throwable e) {
throw new CompletionException(e);
}

View File

@@ -154,6 +154,7 @@ import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.physics.CollisionManager;
@@ -683,6 +684,12 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
*/
private int ticks;
/**
* The number of ticks that have elapsed since the start of this session according to the client.
*/
@Setter
private long clientTicks;
/**
* The world time in ticks according to the server
* <p>
@@ -691,12 +698,6 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
@Setter
private long worldTicks;
/**
* Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator
*/
@Setter
private ScheduledFuture<?> lookBackScheduledFuture = null;
/**
* Used to return players back to their vehicles if the server doesn't want them unmounting.
*/
@@ -1397,15 +1398,27 @@ public class GeyserSession extends FloodgateConnection implements GeyserConnecti
}
/**
* Convenience method to reduce amount of duplicate code. Sends ServerboundUseItemPacket.
* Same as useItem but always default to useTouchRotation false.
*/
public void useItem(Hand hand) {
useItem(hand, false);
}
/**
* Convenience method to reduce amount of duplicate code. Sends ServerboundUseItemPacket.
*/
public void useItem(Hand hand, boolean useTouchRotation) {
if (playerEntity.getFlag(EntityFlag.USING_ITEM)) {
return;
}
sendDownstreamGamePacket(new ServerboundUseItemPacket(
hand, worldCache.nextPredictionSequence(), playerEntity.getYaw(), playerEntity.getPitch()));
float yaw = playerEntity.getYaw(), pitch = playerEntity.getPitch();
if (useTouchRotation) { // Only use touch rotation when we actually needed to, resolve https://github.com/GeyserMC/Geyser/issues/5704
yaw = playerEntity.getBedrockInteractRotation().getY();
pitch = playerEntity.getBedrockInteractRotation().getX();
}
sendDownstreamGamePacket(new ServerboundUseItemPacket(hand, worldCache.nextPredictionSequence(), yaw, pitch));
}
public void releaseItem() {

View File

@@ -290,7 +290,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (packet.getItemInHand() != null && session.getItemMappings().getMapping(packet.getItemInHand()).getJavaItem() instanceof SpawnEggItem) {
if (blockState.is(Blocks.WATER) && blockState.getValue(Properties.LEVEL) == 0) {
// Otherwise causes multiple mobs to spawn - just send a use item packet
useItem(session, packet, blockState.javaId());
useItem(session, packet, blockState.javaId(), false);
break;
}
}
@@ -313,14 +313,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
ItemDefinition definition = packet.getItemInHand().getDefinition();
// Otherwise boats will not be able to be placed in survival and buckets, lily pads, frogspawn, and glass bottles won't work on mobile
if (item instanceof BoatItem || item == Items.LILY_PAD || item == Items.FROGSPAWN) {
useItem(session, packet, blockState.javaId());
useItem(session, packet, blockState.javaId(), true);
} else if (item == Items.GLASS_BOTTLE) {
Block block = blockState.block();
if (!session.isSneaking() && block instanceof CauldronBlock && block != Blocks.WATER_CAULDRON) {
// ServerboundUseItemPacket is not sent for water cauldrons and glass bottles
return;
}
useItem(session, packet, blockState.javaId());
useItem(session, packet, blockState.javaId(), true);
} else if (session.getItemMappings().getBuckets().contains(definition)) {
// Don't send ServerboundUseItemPacket for powder snow buckets
if (definition != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockDefinition()) {
@@ -328,7 +328,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// ServerboundUseItemPacket is not sent for cauldrons and buckets
return;
}
session.setPlacedBucket(useItem(session, packet, blockState.javaId()));
session.setPlacedBucket(useItem(session, packet, blockState.javaId(), true));
} else {
session.setPlacedBucket(true);
}
@@ -598,7 +598,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
return false;
}
private boolean useItem(GeyserSession session, InventoryTransactionPacket packet, int blockState) {
private boolean useItem(GeyserSession session, InventoryTransactionPacket packet, int blockState, boolean useTouchRotation) {
// Update the player's inventory to remove any items added by the client itself
PlayerInventory playerInventory = session.getPlayerInventory();
int heldItemSlot = playerInventory.getOffsetForHotbar(packet.getHotbarSlot());
@@ -633,67 +633,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
}
Vector3f target = packet.getBlockPosition().toFloat().add(packet.getClickPosition());
lookAt(session, target);
session.useItem(Hand.MAIN_HAND);
session.useItem(Hand.MAIN_HAND, useTouchRotation);
return true;
}
/**
* Determine the rotation necessary to activate this transaction.
* <p>
* The position between the intended click position and the player can be determined with two triangles.
* First, we compute the difference of the X and Z coordinates:
* <p>
* Player position (0, 0)
* |
* |
* |
* |_____________ Intended target (-3, 2)
* <p>
* We then use the Pythagorean Theorem to find the direct line (hypotenuse) on the XZ plane. Finding the angle of the
* triangle from there, closest to the player, gives us our yaw rotation value
* Then doing the same using the new XZ distance and Y difference, we can find the direct line of sight from the
* player to the intended target, and the pitch rotation value. We can then send the necessary packets to update
* the player's rotation.
*
* @param session the Geyser Session
* @param target the position to look at
*/
private void lookAt(GeyserSession session, Vector3f target) {
// Use the bounding box's position since we need the player's position seen by the Java server
Vector3d playerPosition = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
float xDiff = (float) (target.getX() - playerPosition.getX());
float yDiff = (float) (target.getY() - (playerPosition.getY() + session.getEyeHeight()));
float zDiff = (float) (target.getZ() - playerPosition.getZ());
// First triangle on the XZ plane
float yaw = (float) -Math.toDegrees(Math.atan2(xDiff, zDiff));
// Second triangle on the Y axis using the hypotenuse of the first triangle as a side
double xzHypot = Math.sqrt(xDiff * xDiff + zDiff * zDiff);
float pitch = (float) -Math.toDegrees(Math.atan2(yDiff, xzHypot));
SessionPlayerEntity entity = session.getPlayerEntity();
ServerboundMovePlayerPosRotPacket returnPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), entity.getYaw(), entity.getPitch());
// This matches Java edition behavior
ServerboundMovePlayerPosRotPacket movementPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), yaw, pitch);
session.sendDownstreamGamePacket(movementPacket);
if (session.getLookBackScheduledFuture() != null) {
session.getLookBackScheduledFuture().cancel(false);
}
if (Math.abs(entity.getYaw() - yaw) > 1f || Math.abs(entity.getPitch() - pitch) > 1f) {
session.setLookBackScheduledFuture(session.scheduleInEventLoop(() -> {
Vector3d newPlayerPosition = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
if (!newPlayerPosition.equals(playerPosition) || entity.getYaw() != returnPacket.getYaw() || entity.getPitch() != returnPacket.getPitch()) {
// The player moved/rotated so there is no need to change their rotation back
return;
}
session.sendDownstreamGamePacket(returnPacket);
}, 150, TimeUnit.MILLISECONDS));
}
}
}

View File

@@ -49,6 +49,10 @@ final class BedrockMovePlayer {
SessionPlayerEntity entity = session.getPlayerEntity();
if (!session.isSpawned()) return;
// We need to save player interact rotation value, as this rotation is used for Touch device and indicate where the player is touching.
// This is needed so that we can interact with where player actually touch on the screen on Bedrock and not just from the center of the screen.
entity.setBedrockInteractRotation(packet.getInteractRotation());
// Ignore movement packets until Bedrock's position matches the teleported position
if (session.getUnconfirmedTeleport() != null) {
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
@@ -79,13 +83,6 @@ final class BedrockMovePlayer {
boolean positionChangedAndShouldUpdate = !hasVehicle && (session.getInputCache().shouldSendPositionReminder() || actualPositionChanged);
boolean rotationChanged = hasVehicle || (entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw);
if (session.getLookBackScheduledFuture() != null) {
// Resend the rotation if it was changed by Geyser
rotationChanged |= !session.getLookBackScheduledFuture().isDone();
session.getLookBackScheduledFuture().cancel(false);
session.setLookBackScheduledFuture(null);
}
// Simulate jumping since it happened this tick, not from the last tick end.
if (entity.isOnGround() && packet.getInputData().contains(PlayerAuthInputData.START_JUMPING)) {
entity.setLastTickEndVelocity(Vector3f.from(entity.getLastTickEndVelocity().getX(), Math.max(entity.getLastTickEndVelocity().getY(), entity.getJumpVelocity()), entity.getLastTickEndVelocity().getZ()));

View File

@@ -75,6 +75,8 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
public void translate(GeyserSession session, PlayerAuthInputPacket packet) {
SessionPlayerEntity entity = session.getPlayerEntity();
session.setClientTicks(packet.getTick());
boolean wasJumping = session.getInputCache().wasJumping();
session.getInputCache().processInputs(entity, packet);

View File

@@ -89,7 +89,7 @@ public final class AssetUtils {
// Get the url for the latest version of the games manifest
String latestInfoURL = "";
for (Version version : versionManifest.getVersions()) {
if (version.getId().equals(GameProtocol.getJavaCodec().getMinecraftVersion())) {
if (version.getId().equals(GameProtocol.getJavaMinecraftVersion())) {
latestInfoURL = version.getUrl();
break;
}

View File

@@ -50,6 +50,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -223,7 +224,9 @@ public class WebUtils {
con.getHeaderField("ETag"),
String.valueOf(con.getLastModified()),
downloadLocation.getFileName().toString()
));
),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
packMetadata.toFile().setLastModified(System.currentTimeMillis());
} catch (IOException e) {
Files.delete(packMetadata);

View File

@@ -6,7 +6,7 @@ erosion = "1.1-20240521.000109-3"
events = "1.1-SNAPSHOT"
jackson = "2.17.0"
fastutil = "8.5.15-SNAPSHOT"
netty = "4.2.1.Final"
netty = "4.2.3.Final"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
@@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta7-20250705.103024-11"
protocol-codec = "3.0.0.Beta7-20250705.103024-11"
raknet = "1.0.0.CR3-20250218.160705-18"
minecraftauth = "4.1.1"
mcprotocollib = "1.21.7-20250712.190650-3"
mcprotocollib = "1.21.7-20250725.134643-4"
adventure = "4.21.0"
adventure-platform = "4.3.0"
junit = "5.9.2"
@@ -36,13 +36,13 @@ viaproxy = "3.3.2-SNAPSHOT"
fabric-loader = "0.16.14"
fabric-api = "0.128.1+1.21.7"
fabric-permissions-api = "0.4.0-SNAPSHOT"
neoforge-minecraft = "21.7.0-beta"
neoforge-minecraft = "21.8.0-beta"
mixin = "0.8.5"
mixinextras = "0.3.5"
minecraft = "1.21.7"
minecraft = "1.21.8"
mockito = "5.+"
runtask = "2.3.1"
runpaperversion = "1.21.7"
runpaperversion = "1.21.8"
runvelocityversion = "3.4.0-SNAPSHOT"
# plugin versions
@@ -88,8 +88,6 @@ adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text
adventure-text-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
adventure-text-serializer-bungeecord = { group = "net.kyori", name = "adventure-text-serializer-bungeecord", version.ref = "adventure-platform" }
netty-resolver-dns = { group = "io.netty", name = "netty-resolver-dns", version.ref = "netty" }
netty-resolver-dns-native-macos = { group = "io.netty", name = "netty-resolver-dns-native-macos", version.ref = "netty" }
netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", version.ref = "netty" }
netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" }