Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bootstrap/mod/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ dependencies {
api(projects.core)
compileOnly(libs.mixin)
compileOnly(libs.mixinextras)
compileOnly(libs.asm)
compileOnlyApi(libs.viaversion)

// Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
compileOnly(libs.fabric.loader)
Expand Down
10 changes: 10 additions & 0 deletions bootstrap/mod/fabric/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ dependencies {

modImplementation(libs.cloud.fabric)
include(libs.cloud.fabric)

include("org.incendo:cloud-fabric:2.0.0-beta.11")
include("org.incendo:cloud-fabric:2.0.0-beta.10")
include("org.incendo:cloud-fabric:2.0.0-beta.9")
include("org.incendo:cloud-fabric:2.0.0-beta.8")
include("org.incendo:cloud-fabric:2.0.0-beta.7")
include("org.incendo:cloud-fabric:2.0.0-beta.6")
include("org.incendo:cloud-fabric:2.0.0-beta.5")
include("org.incendo:cloud-fabric:2.0.0-beta.4")
Comment thread
Novampr marked this conversation as resolved.
Outdated

include(libs.fabric.permissions.api)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,33 @@
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
import org.geysermc.geyser.platform.mod.ModConstants;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.fabric.FabricServerCommandManager;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {

public GeyserFabricBootstrap() {
Expand Down Expand Up @@ -73,22 +87,96 @@ public void onInitialize() {
}
});

ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
// ServerPlayer#createCommandSourceStack isn't on all versions
if (ModConstants.isModernVersion()) {
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
}
Comment thread
Novampr marked this conversation as resolved.
Outdated

this.onGeyserInitialize();

var sourceConverter = CommandSourceConverter.layered(
if (ModConstants.isModernVersion()) {
Function<ServerPlayer, CommandSourceStack> stackCreator = player -> {
return player.createCommandSourceStack();
// if (ModConstants.CURRENT_PROTOCOL >= 768) {
// return player.createCommandSourceStack();
// } else {
// try {
// // Older version support time
//
// List<String> positionFields = List.of("position", "field_22467");
//
// Field positionField = null;
//
// for (String methodName : positionFields) {
// try {
// positionField = Entity.class.getDeclaredField(methodName);
// positionField.setAccessible(true);
// break;
// } catch (NoSuchFieldException ignored) {}
// }
//
// if (positionField == null) {
// throw new RuntimeException("Unable to get position from ServerPlayer.");
// }
//
// List<String> levelMethods = List.of("getLevel", "method_14220", "serverLevel", "method_51469");
//
// Method levelMethod = null;
//
// for (String methodName : levelMethods) {
// try {
// levelMethod = player.getClass().getMethod(methodName);
// break;
// } catch (NoSuchMethodException ignored) {}
// }
//
// if (levelMethod == null) {
// throw new RuntimeException("Unable to get level from ServerPlayer.");
// }
//
// List<String> serverFields = List.of("server", "field_13995");
//
// Field serverField = null;
//
// for (String methodName : serverFields) {
// try {
// serverField = player.getClass().getDeclaredField(methodName);
// serverField.setAccessible(true);
// break;
// } catch (NoSuchFieldException ignored) {}
// }
//
// if (serverField == null) {
// throw new RuntimeException("Unable to get server from ServerPlayer.");
// }
//
// // Double casting as in older versions, player is instance of CommandSource, but not in modern versions
// //noinspection RedundantCast
// return new CommandSourceStack((CommandSource) (Object) player, (Vec3) positionField.get(player), player.getRotationVector(), (ServerLevel) levelMethod.invoke(player), player.getPermissionLevel(), player.getName().getString(), player.getDisplayName(), (MinecraftServer) serverField.get(player), player);
// } catch (IllegalAccessException | InvocationTargetException e) {
// throw new RuntimeException(e);
// }
// }
};

var sourceConverter = CommandSourceConverter.layered(
Comment thread
Novampr marked this conversation as resolved.
Outdated
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
ServerPlayer::createCommandSourceStack,
stackCreator,
() -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
ModCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new FabricServerCommandManager<>(
);
CommandManager<GeyserCommandSource> cloud = new FabricServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
);

this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
} else { // Fallback to a standalone command manager
CommandManager<GeyserCommandSource> cloud = new StandaloneCloudCommandManager(GeyserImpl.getInstance());

this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud));
}
Comment thread
Novampr marked this conversation as resolved.
Outdated
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.jetbrains.annotations.NotNull;
Comment thread
Novampr marked this conversation as resolved.
Outdated

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -80,6 +81,11 @@ public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap)
return false;
}

@Override
public boolean testViaPresent(@NonNull GeyserModBootstrap bootstrap) {
return FabricLoader.getInstance().isModLoaded("viafabric");
}

@Override
public @Nullable InputStream resolveResource(@NonNull String resource) {
// We need to handle this differently, because Fabric shares the classloader across multiple mods
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/mod/fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
"depends": {
"fabricloader": ">=0.16.7",
"fabric-api": "*",
"minecraft": ">=1.21.6"
"minecraft": "*"
Comment thread
Novampr marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap)
return false;
}

@Override
public boolean testViaPresent(@NonNull GeyserModBootstrap bootstrap) {
return ModList.get().isLoaded("viaforge");
}

@Override
public @Nullable InputStream resolveResource(@NonNull String resource) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.minecraft.SharedConstants;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -54,7 +55,6 @@

@RequiredArgsConstructor
public abstract class GeyserModBootstrap implements GeyserBootstrap {

@Getter
private static GeyserModBootstrap instance;

Expand Down Expand Up @@ -85,6 +85,15 @@ public void onGeyserInitialize() {
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);

if (!ModConstants.isModernVersion() && !this.platform.testViaPresent(this)) {
this.geyserLogger.error("-------------------------------------------------------------------------");
this.geyserLogger.error("The current server version is not supported unless ViaVersion is present!");
this.geyserLogger.error("Geyser will not start unless you add ViaVersion!");
this.geyserLogger.error("-------------------------------------------------------------------------");
return;
}
Comment on lines +93 to +99
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also throw a runtime exception :p
Or, alternatively, make the older adapters depend on via? dunno if that'd work on neo, I guess we'll see

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should try depend in the adapters, but if that causes Neo issues, I think the current solution works good enough


this.geyser = GeyserImpl.load(this.platform.platformType(), this);
}

Expand All @@ -104,7 +113,7 @@ public void onGeyserEnable() {

GeyserImpl.start();

if (geyserConfig.isLegacyPingPassthrough()) {
if (geyserConfig.isLegacyPingPassthrough() || !ModConstants.isModernVersion()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
Expand All @@ -115,12 +124,12 @@ public void onGeyserEnable() {
return;
}

this.geyserWorldManager = new GeyserModWorldManager(server);
this.geyserWorldManager = ModConstants.isModernVersion() ? new GeyserModWorldManager(server) : null;

// We want to do this late in the server startup process to allow other mods
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserModInjector(server, this.platform);
if (isServer()) {
this.geyserInjector = ModConstants.isModernVersion() ? new GeyserModInjector(server, this.platform) : null;
if (isServer() && this.geyserInjector != null) {
this.geyserInjector.initializeLocalChannel(this);
}
}
Expand Down Expand Up @@ -166,7 +175,7 @@ public IGeyserPingPassthrough getGeyserPingPassthrough() {

@Override
public WorldManager getWorldManager() {
return geyserWorldManager;
return geyserWorldManager == null ? DEFAULT_CHUNK_MANAGER : geyserWorldManager;
}

@Override
Expand All @@ -181,7 +190,7 @@ public BootstrapDumpInfo getDumpInfo() {

@Override
public String getMinecraftServerVersion() {
return this.server.getServerVersion();
return ModConstants.CURRENT_VERSION;
}

@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
Expand All @@ -194,12 +203,12 @@ public String getServerBindAddress() {

@Override
public SocketAddress getSocketAddress() {
return this.geyserInjector.getServerSocketAddress();
return this.geyserInjector == null ? null : this.geyserInjector.getServerSocketAddress();
}

@Override
public int getServerPort() {
if (isServer()) {
if (isServer() && ModConstants.isModernVersion()) {
return ((GeyserServerPortGetter) server).geyser$getServerPort();
} else {
// Set in the IntegratedServerMixin
Expand All @@ -214,6 +223,11 @@ public boolean testFloodgatePluginPresent() {
return this.platform.testFloodgatePluginPresent(this);
}

@Override
public boolean isStandaloneCommandManager() {
return !ModConstants.isModernVersion();
}

@Nullable
@Override
public InputStream getResourceOrNull(String resource) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2025 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.platform.mod;

import net.minecraft.DetectedVersion;
import net.minecraft.WorldVersion;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

public class GeyserModMixinPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String s) {

}

@Override
public String getRefMapperConfig() {
return "";
}

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
WorldVersion worldVersion = DetectedVersion.tryDetectVersion();

List<String> potentialMethods = List.of("name", "getName", "comp_4024", "method_48019");

Method nameMethod = null;

for (String methodName : potentialMethods) {
try {
nameMethod = worldVersion.getClass().getMethod(methodName);
break;
} catch (NoSuchMethodException ignored) {}
}

if (nameMethod == null) throw new IllegalStateException("Unable to determine suitable method for getting Minecraft version.");

try {
return !mixinClassName.startsWith("org.geysermc.geyser.platform.mod.mixin") || nameMethod.invoke(worldVersion).equals(ModConstants.MODERN_VERSION);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); // This hopefully, will never happen
}
}

@Override
public void acceptTargets(Set<String> set, Set<String> set1) {

}

@Override
public List<String> getMixins() {
return List.of();
}

@Override
public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {

}

@Override
public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {

}
}
Loading