Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
Sonic2423 committed Jun 7, 2024
1 parent b58884a commit a835c7d
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 6 deletions.
12 changes: 6 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ loader_version_range=[2,)

# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
mod_id=examplemod
mod_id=neoforwarding
# The human-readable display name for the mod.
mod_name=Example Mod
mod_name=NeoForwarding
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
mod_license=GNU GPLv3
# The mod version. See https://semver.org/
mod_version=1.0.0
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
mod_group_id=com.example.examplemod
mod_group_id=com.sonic2423.neoforwarding
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=YourNameHere, OtherNameHere
mod_authors=Sonic2423
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=Example mod description.\nNewline characters can be used and will be replaced properly.
mod_description=Enables modern player information forwarding in NeoForge servers for use with Velocity.
47 changes: 47 additions & 0 deletions src/main/java/com/sonic2423/neoforwarding/Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sonic2423.neoforwarding;

import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.config.ModConfigEvent;
import net.neoforged.neoforge.common.ModConfigSpec;

import static com.sonic2423.neoforwarding.NeoForwarding.LOGGER;

@Mod.EventBusSubscriber(modid = NeoForwarding.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Config {
public static boolean enableForwarding;
public static String forwardingSecret;

private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();

public static final ModConfigSpec.ConfigValue<String> FORWARDING_SECRET = BUILDER
.comment("Use the 'forwarding.secret' from Velocity (and not the default value of '') and insert it here")
.define("forwardingSecret", "");

private static final ModConfigSpec.BooleanValue ENABLE_FORWARDING = BUILDER
.comment("This must be enabled after you inserted your forwarding secret for the server to accept and send forwarding requests.\nIf disabled the server will act as if the mod is not installed.")
.define("enableForwarding", false);

static final ModConfigSpec SPEC = BUILDER.build();

@SubscribeEvent
static void onLoad(final ModConfigEvent event) {
enableForwarding = ENABLE_FORWARDING.get();
forwardingSecret = FORWARDING_SECRET.get();

if (enableForwarding) {
if (Config.forwardingSecret.isEmpty()) {
LOGGER.warn("Please specify a forwarding secret.");
LOGGER.warn("NeoForwarding will be disabled!");
enableForwarding = false;
} else if (Config.forwardingSecret.length() != 12) {
LOGGER.error("Malformed modern forwarding secret.");
LOGGER.error("It is very likely that no one can log in!");
} else {
LOGGER.info("Modern forwarding enabled.");
}
} else {
LOGGER.info("Modern forwarding disabled.");
}
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/sonic2423/neoforwarding/NeoForwarding.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sonic2423.neoforwarding;

import com.mojang.logging.LogUtils;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import org.slf4j.Logger;

// The value here should match an entry in the META-INF/mods.toml file
@Mod(NeoForwarding.MODID)
public class NeoForwarding {
// Define mod id in a common place for everything to reference
public static final String MODID = "neoforwarding";
// Directly reference a slf4j logger
public static final Logger LOGGER = LogUtils.getLogger();

// The constructor for the mod class is the first code that is run when your mod is loaded.
// FML will recognize some parameter types like IEventBus or ModContainer and pass them in automatically.
public NeoForwarding(IEventBus modEventBus) {
// Register our mod's ModConfigSpec so that FML can create and load the config file for us
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.sonic2423.neoforwarding;

import com.google.common.net.InetAddresses;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload;
import net.minecraft.network.protocol.login.custom.CustomQueryPayload;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import static com.sonic2423.neoforwarding.Config.forwardingSecret;

/*
* The following is ported from "Paper" with slight modifications to work with NeoForge as Mixin.
* See: https://github.com/PaperMC/Paper/blob/bd5867a96f792f0eb32c1d249bb4bbc1d8338d14/patches/server/0748-Add-Velocity-IP-Forwarding-Support.patch
* PlayerDataForwarding: Lines 51-107.
*/
public class PlayerDataForwarding {
private static final int SUPPORTED_FORWARDING_VERSION = 1;
public static final int MODERN_FORWARDING_WITH_KEY = 2;
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
public static final int MODERN_LAZY_SESSION = 4;
public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION;
public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info");

public static boolean checkIntegrity(final FriendlyByteBuf buf) {

final byte[] signature = new byte[32];
buf.readBytes(signature);

final byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);

try {
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(forwardingSecret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256"));
final byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature)) {
return false;
}
} catch (final InvalidKeyException | NoSuchAlgorithmException e) {
throw new AssertionError(e);
}

return true;
}

public static InetAddress readAddress(final FriendlyByteBuf buf) {
return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE));
}

public static GameProfile createProfile(final FriendlyByteBuf buf) {
final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16));
readProperties(buf, profile);
return profile;
}

private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) {
final int properties = buf.readVarInt();
for (int i1 = 0; i1 < properties; i1++) {
final String name = buf.readUtf(Short.MAX_VALUE);
final String value = buf.readUtf(Short.MAX_VALUE);
final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null;
profile.getProperties().put(name, new Property(name, value, signature));
}
}

public record VelocityPlayerInfoPayload(FriendlyByteBuf buffer) implements CustomQueryPayload {

public static final ResourceLocation id = PLAYER_INFO_CHANNEL;

@Override
public void write(final FriendlyByteBuf buf) {
buf.writeBytes(this.buffer);
}

@Override
public @NotNull ResourceLocation id() {
return id;
}
}

public record QueryAnswerPayload(FriendlyByteBuf buffer) implements CustomQueryAnswerPayload {

@Override
public void write(final FriendlyByteBuf buf) {
buf.writeBytes(this.buffer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sonic2423.neoforwarding.mixin;

import java.net.SocketAddress;

public interface ISetAddressInConnection {
void neoproxy$setAddress(SocketAddress address);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.sonic2423.neoforwarding.mixin.mixins;

import com.mojang.brigadier.arguments.ArgumentType;
import io.netty.buffer.Unpooled;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import static com.sonic2423.neoforwarding.NeoForwarding.LOGGER;

/*
* The following is ported from "CrossStitch" with slight modifications to work with NeoForge.
* See: https://github.com/VelocityPowered/CrossStitch/blob/f8d6be1128cb049e5c5a93068b9069e0838a2200/src/main/java/com/velocitypowered/crossstitch/mixin/command/CommandTreeSerializationMixin.java
*/
@Mixin(targets = "net.minecraft.network.protocol.game.ClientboundCommandsPacket$ArgumentNodeStub")
public abstract class CrossStitchSupport {

@Unique
private static final int MOD_ARGUMENT_INDICATOR = -256;

@Inject(method = "serializeCap(Lnet/minecraft/network/FriendlyByteBuf;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo$Template;)V", at = @At("HEAD"), cancellable = true)
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void writeNode$wrapInVelocityModArgument(FriendlyByteBuf pBuffer, ArgumentTypeInfo<A, T> pArgumentInfo, ArgumentTypeInfo.Template<A> pArgumentInfoTemplate, CallbackInfo ci) {
ResourceLocation key = BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getKey(pArgumentInfo);
int id = BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getId(pArgumentInfo);

if (key == null || key.getNamespace().equals("brigadier") || (key.getNamespace().equals("minecraft") && !key.getPath().equals("test_argument") && !key.getPath().equals("test_class"))) {
return;
}

LOGGER.debug("Mod argument type: {}: {}:{}", id, key.getNamespace(), key.getPath());

ci.cancel();

// Not a standard Minecraft argument type - so we need to wrap it
neoproxy$serializeWrappedArgumentType(pBuffer, pArgumentInfo, pArgumentInfoTemplate);
}

@Unique
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void neoproxy$serializeWrappedArgumentType(FriendlyByteBuf pBuffer, ArgumentTypeInfo<A, T> pArgumentInfo, ArgumentTypeInfo.Template<A> pArgumentInfoTemplate) {
pBuffer.writeVarInt(MOD_ARGUMENT_INDICATOR);
pBuffer.writeVarInt(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getId(pArgumentInfo));

FriendlyByteBuf extraData = new FriendlyByteBuf(Unpooled.buffer());
pArgumentInfo.serializeToNetwork((T) pArgumentInfoTemplate, extraData);

pBuffer.writeVarInt(extraData.readableBytes());
pBuffer.writeBytes(extraData);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.sonic2423.neoforwarding.mixin.mixins;

import com.mojang.authlib.GameProfile;
import com.sonic2423.neoforwarding.Config;
import com.sonic2423.neoforwarding.PlayerDataForwarding;
import com.sonic2423.neoforwarding.mixin.ISetAddressInConnection;
import net.minecraft.network.Connection;
import net.minecraft.network.TickablePacketListener;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.login.ServerLoginPacketListener;
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import javax.annotation.Nullable;

import static com.sonic2423.neoforwarding.NeoForwarding.LOGGER;

/*
* The following is ported from "Paper" with slight modifications to work with NeoForge as Mixin.
* See: https://github.com/PaperMC/Paper/blob/bd5867a96f792f0eb32c1d249bb4bbc1d8338d14/patches/server/0748-Add-Velocity-IP-Forwarding-Support.patch
* handleHello: Lines 152-161.
* onHandleCustomQueryPacket: Lines 182-226.
*/
@Mixin(ServerLoginPacketListenerImpl.class)
public abstract class ServerLoginPacketListenerImplMixin implements ServerLoginPacketListener, TickablePacketListener {

@Unique
private int neoforwarding$velocityLoginMessageId = -1;
@Final
@Shadow
private Connection connection;
@Shadow
@Nullable
private GameProfile authenticatedProfile;

@Shadow
private void disconnect(Component pReason) {
}

@Shadow
private void startClientVerification(GameProfile authenticatedProfile) {
}

// using specific injection target because it is very unlikely that something else will inject in this function.
@Inject(method = "handleHello", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerLoginPacketListenerImpl;startClientVerification(Lcom/mojang/authlib/GameProfile;)V", ordinal = 1), cancellable = true)
public void handleHello(ServerboundHelloPacket pPacket, CallbackInfo ci) {
if (Config.enableForwarding) {
this.neoforwarding$velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt();
net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer());
buf.writeByte(PlayerDataForwarding.MAX_SUPPORTED_FORWARDING_VERSION);
net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 =
new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(
this.neoforwarding$velocityLoginMessageId, new PlayerDataForwarding.VelocityPlayerInfoPayload(buf)
);
this.connection.send(packet1);
ci.cancel();
}
}

@Inject(method = "handleCustomQueryPacket", at = @At("HEAD"), cancellable = true)
private void onHandleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet, CallbackInfo ci) {
if (Config.enableForwarding && packet.transactionId() == this.neoforwarding$velocityLoginMessageId) {

if (packet.payload() == null) {
this.disconnect(Component.literal("This server requires you to connect with Velocity."));
return;
}

PlayerDataForwarding.QueryAnswerPayload payload = (PlayerDataForwarding.QueryAnswerPayload) packet.payload();

net.minecraft.network.FriendlyByteBuf buf = payload.buffer();

if (!PlayerDataForwarding.checkIntegrity(buf)) {
this.disconnect(Component.literal("Unable to verify player details"));
return;
}

int version = buf.readVarInt();
if (version > PlayerDataForwarding.MAX_SUPPORTED_FORWARDING_VERSION) {
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + PlayerDataForwarding.MAX_SUPPORTED_FORWARDING_VERSION);
}

java.net.SocketAddress listening = this.connection.getRemoteAddress();
int port = 0;
if (listening instanceof java.net.InetSocketAddress) {
port = ((java.net.InetSocketAddress) listening).getPort();
}

ISetAddressInConnection setAddressMixin = (ISetAddressInConnection) this.connection;
setAddressMixin.neoproxy$setAddress(new java.net.InetSocketAddress(PlayerDataForwarding.readAddress(buf), port));

startClientVerification(PlayerDataForwarding.createProfile(buf));

LOGGER.info("UUID of player {} is {}", this.authenticatedProfile.getName(), this.authenticatedProfile.getId());

ci.cancel();
}
}
}
Loading

0 comments on commit a835c7d

Please sign in to comment.