Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It also parses passed IP addresses so the server is aware of the real player IP

### Compatibility

TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.

When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
This does not apply when using Paper 1.16 build #503 or higher.
Expand All @@ -30,4 +30,5 @@ See [Contact](https://docs.tcpshield.com/about-us)
These wonderful contributors have helped TCPShield make this plugin better!

* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ buildscript {
plugins {
id 'java'
id 'idea'
id 'fabric-loom' version '0.6-SNAPSHOT'
}

repositories {
Expand All @@ -40,6 +41,9 @@ repositories {
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = 'https://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
Expand All @@ -59,6 +63,11 @@ dependencies {
// Velocity
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'

// Fabric
minecraft "com.mojang:minecraft:1.16.5"
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
modImplementation group: 'net.fabricmc', name: 'fabric-loader', version: '0.11.2'

// Testing
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'
Expand Down
10 changes: 10 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
18 changes: 18 additions & 0 deletions src/main/java/net/tcpshield/tcpshield/fabric/TCPShieldFabric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.tcpshield.tcpshield.fabric;

import net.fabricmc.api.ModInitializer;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.fabric.impl.FabricConfigImpl;

import java.util.logging.Logger;

public class TCPShieldFabric implements ModInitializer {

public static final Logger LOGGER = Logger.getLogger("TCPShield");
public static final HandshakePacketHandler PACKET_HANDLER = new HandshakePacketHandler(LOGGER, new FabricConfigImpl());

@Override
public void onInitialize() {
LOGGER.info("TCPShield has been loaded.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package net.tcpshield.tcpshield.fabric.impl;

import net.fabricmc.loader.api.FabricLoader;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class FabricConfigImpl extends TCPShieldConfig {

private final Path configLocation = new File(FabricLoader.getInstance().getConfigDir().toString(), "tcpshield.yml").toPath();

public FabricConfigImpl() {
if(!configExists()) {
createDefaultConfig();
}

ConfigData config = loadConfig();

this.onlyProxy = config.onlyAllowProxyConnections;
this.ipWhitelistFolder = new File(FabricLoader.getInstance().getGameDirectory(), "ip-whitelist");
this.geyser = false;
this.debug = config.debugMode;
}

/**
* @return whether the file specified in {@link FabricConfigImpl#configLocation} exists.
*/
private boolean configExists() {
return Files.exists(configLocation);
}

/**
* Creates the default configuration file at the location specified in {@link FabricConfigImpl#configLocation}.
*
* <p>
* This operation will always override an existing configuration file if it exists.
* Callers should check {@link FabricConfigImpl#configExists()} if they wish to avoid this behavior.
*/
private void createDefaultConfig() {
// Ensure the parent directory of our config file exists.
// If it does not exist, attempt to create it now.
Path parentDirectory = configLocation.getParent();
if (!Files.exists(parentDirectory) || !Files.isDirectory(parentDirectory)) {
parentDirectory.toFile().mkdirs();
}

// Copy the config.yml data from our mod jar to the loader config folder.
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
Files.copy(in, configLocation);
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}

private ConfigData loadConfig() {
try {
List<String> strings = Files.readAllLines(configLocation);

boolean onlyAllowProxyConnections = true;
boolean debugMode = false;

// Rudimentary config parsing
for (String line : strings) {
String[] keyValue = line.split(": ");

// A config option will only be valid if it is in the format 'a: b'.
// Ensure that is the case now.
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];

switch (key) {
case "only-allow-proxy-connections":
onlyAllowProxyConnections = Boolean.parseBoolean(value);
break;
case "debug-mode":
debugMode = Boolean.parseBoolean(value);
break;
}
}
}

return new ConfigData(onlyAllowProxyConnections, debugMode);
} catch (Exception e) {
throw new TCPShieldInitializationException("Couldn't load config in config/tclshield.yml!");
}
}

private static class ConfigData {

protected boolean onlyAllowProxyConnections;
protected boolean debugMode;

public ConfigData(boolean onlyAllowProxyConnections, boolean debugMode) {
this.onlyAllowProxyConnections = onlyAllowProxyConnections;
this.debugMode = debugMode;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.fabric.impl;

import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.fabric.mixin.HandshakeC2SPacketAccessor;

public class FabricPacketImpl implements IPacket {

private final HandshakeC2SPacket handshake;

public FabricPacketImpl(HandshakeC2SPacket handshake) {
this.handshake = handshake;
}

@Override
public String getRawPayload() {
return ((HandshakeC2SPacketAccessor) handshake).getAddress();
}

@Override
public void modifyOriginalPacket(String hostname) throws Exception {
// NO OPERATION
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package net.tcpshield.tcpshield.fabric.impl;

import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
import net.minecraft.text.LiteralText;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.fabric.mixin.ClientConnectionAccessor;

import java.net.InetSocketAddress;

public class FabricPlayerImpl implements IPlayer {

private final ClientConnection connection;
private String ip;

public FabricPlayerImpl(HandshakeC2SPacket packet, ClientConnection connection) {
this.connection = connection;
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}

@Override
public String getIP() {
return ip;
}

@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
// At this point, the IP/connection believe the player has the IP of TCPShield.
// The ip passed into this method contains their CORRECT data, which we have to assign to the player network connection.
((ClientConnectionAccessor) connection).setAddress(ip);
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}

@Override
public void disconnect() {
connection.disconnect(new LiteralText("Connection failed. Please try again or contact an administrator."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.tcpshield.tcpshield.fabric.mixin;

import io.netty.channel.Channel;
import net.minecraft.network.ClientConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

import java.net.SocketAddress;

@Mixin(ClientConnection.class)
public interface ClientConnectionAccessor {
@Accessor
void setAddress(SocketAddress address);

@Accessor
Channel getChannel();

@Accessor
SocketAddress getAddress();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.tcpshield.tcpshield.fabric.mixin;

import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(HandshakeC2SPacket.class)
public interface HandshakeC2SPacketAccessor {
@Accessor
String getAddress();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.tcpshield.tcpshield.fabric.mixin;

import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.server.network.ServerHandshakeNetworkHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.fabric.TCPShieldFabric;
import net.tcpshield.tcpshield.fabric.impl.FabricPacketImpl;
import net.tcpshield.tcpshield.fabric.impl.FabricPlayerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(ServerHandshakeNetworkHandler.class)
public class ServerHandshakeMixin {

@Shadow @Final private ClientConnection connection;

@Inject(
method = "onHandshake",
at = @At("HEAD"))
private void onHandshake(HandshakeC2SPacket handshake, CallbackInfo ci) {
IPacket packet = new FabricPacketImpl(handshake);
IPlayer player = new FabricPlayerImpl(handshake, connection);

TCPShieldFabric.PACKET_HANDLER.onHandshake(packet, player);
}
}
27 changes: 27 additions & 0 deletions src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"schemaVersion": 1,
"id": "tcp-shield",
"version": "2.5",
"name": "TCPShield",
"description": "TCPShield IP parsing capabilities for Fabric",
"authors": [
"TCPShield"
],
"contact": {
"homepage": "https://tcpshield.com"
},
"license": "MIT",
"environment": "*",
"entrypoints": {
"main": [
"net.tcpshield.tcpshield.fabric.TCPShieldFabric"
]
},
"mixins": [
"tcpshield.mixins.json"
],
"depends": {
"fabricloader": "*",
"minecraft": "*"
}
}
14 changes: 14 additions & 0 deletions src/main/resources/tcpshield.mixins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.tcpshield.tcpshield.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"ClientConnectionAccessor",
"HandshakeC2SPacketAccessor",
"ServerHandshakeMixin"
],
"injectors": {
"defaultRequire": 1
}
}