forked from Sonic2423/NeoForwarding
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
517 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
24
src/main/java/com/sonic2423/neoforwarding/NeoForwarding.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/main/java/com/sonic2423/neoforwarding/PlayerDataForwarding.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/sonic2423/neoforwarding/mixin/ISetAddressInConnection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
56 changes: 56 additions & 0 deletions
56
src/main/java/com/sonic2423/neoforwarding/mixin/mixins/CrossStitchSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
107 changes: 107 additions & 0 deletions
107
...ain/java/com/sonic2423/neoforwarding/mixin/mixins/ServerLoginPacketListenerImplMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
Oops, something went wrong.