Skip to content

Commit 740e648

Browse files
authored
TCPShield Fabric support (#40)
Adds Fabric support (huge, huge thanks to @Draylar)
1 parent dd51276 commit 740e648

12 files changed

Lines changed: 312 additions & 1 deletion

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ It also parses passed IP addresses so the server is aware of the real player IP
66

77
### Compatibility
88

9-
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
9+
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.
1010

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

3232
* [Paul Zhang](https://github.com/paulzhng)
33+
* [Draylar](https://github.com/Draylar)
3334
* [RyanDeLap](https://github.com/RyanDeLap)

build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ buildscript {
1919
plugins {
2020
id 'java'
2121
id 'idea'
22+
id 'fabric-loom' version '0.6-SNAPSHOT'
2223
}
2324

2425
repositories {
@@ -40,6 +41,9 @@ repositories {
4041
maven {
4142
url = 'https://repo.velocitypowered.com/snapshots/'
4243
}
44+
maven {
45+
url = 'https://maven.fabricmc.net/'
46+
}
4347
flatDir {
4448
dirs '/libs'
4549
}
@@ -59,6 +63,11 @@ dependencies {
5963
// Velocity
6064
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'
6165

66+
// Fabric
67+
minecraft "com.mojang:minecraft:1.16.5"
68+
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
69+
modImplementation group: 'net.fabricmc', name: 'fabric-loader', version: '0.11.2'
70+
6271
// Testing
6372
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
6473
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'

settings.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pluginManagement {
2+
repositories {
3+
jcenter()
4+
maven {
5+
name = 'Fabric'
6+
url = 'https://maven.fabricmc.net/'
7+
}
8+
gradlePluginPortal()
9+
}
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package net.tcpshield.tcpshield.fabric;
2+
3+
import net.fabricmc.api.ModInitializer;
4+
import net.tcpshield.tcpshield.HandshakePacketHandler;
5+
import net.tcpshield.tcpshield.fabric.impl.FabricConfigImpl;
6+
7+
import java.util.logging.Logger;
8+
9+
public class TCPShieldFabric implements ModInitializer {
10+
11+
public static final Logger LOGGER = Logger.getLogger("TCPShield");
12+
public static final HandshakePacketHandler PACKET_HANDLER = new HandshakePacketHandler(LOGGER, new FabricConfigImpl());
13+
14+
@Override
15+
public void onInitialize() {
16+
LOGGER.info("TCPShield has been loaded.");
17+
}
18+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package net.tcpshield.tcpshield.fabric.impl;
2+
3+
import net.fabricmc.loader.api.FabricLoader;
4+
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
5+
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.List;
13+
14+
public class FabricConfigImpl extends TCPShieldConfig {
15+
16+
private final Path configLocation = new File(FabricLoader.getInstance().getConfigDir().toString(), "tcpshield.yml").toPath();
17+
18+
public FabricConfigImpl() {
19+
if(!configExists()) {
20+
createDefaultConfig();
21+
}
22+
23+
ConfigData config = loadConfig();
24+
25+
this.onlyProxy = config.onlyAllowProxyConnections;
26+
this.ipWhitelistFolder = new File(FabricLoader.getInstance().getGameDirectory(), "ip-whitelist");
27+
this.geyser = false;
28+
this.debug = config.debugMode;
29+
}
30+
31+
/**
32+
* @return whether the file specified in {@link FabricConfigImpl#configLocation} exists.
33+
*/
34+
private boolean configExists() {
35+
return Files.exists(configLocation);
36+
}
37+
38+
/**
39+
* Creates the default configuration file at the location specified in {@link FabricConfigImpl#configLocation}.
40+
*
41+
* <p>
42+
* This operation will always override an existing configuration file if it exists.
43+
* Callers should check {@link FabricConfigImpl#configExists()} if they wish to avoid this behavior.
44+
*/
45+
private void createDefaultConfig() {
46+
// Ensure the parent directory of our config file exists.
47+
// If it does not exist, attempt to create it now.
48+
Path parentDirectory = configLocation.getParent();
49+
if (!Files.exists(parentDirectory) || !Files.isDirectory(parentDirectory)) {
50+
parentDirectory.toFile().mkdirs();
51+
}
52+
53+
// Copy the config.yml data from our mod jar to the loader config folder.
54+
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
55+
Files.copy(in, configLocation);
56+
} catch (Exception e) {
57+
throw new TCPShieldInitializationException(e);
58+
}
59+
}
60+
61+
private ConfigData loadConfig() {
62+
try {
63+
List<String> strings = Files.readAllLines(configLocation);
64+
65+
boolean onlyAllowProxyConnections = true;
66+
boolean debugMode = false;
67+
68+
// Rudimentary config parsing
69+
for (String line : strings) {
70+
String[] keyValue = line.split(": ");
71+
72+
// A config option will only be valid if it is in the format 'a: b'.
73+
// Ensure that is the case now.
74+
if (keyValue.length == 2) {
75+
String key = keyValue[0];
76+
String value = keyValue[1];
77+
78+
switch (key) {
79+
case "only-allow-proxy-connections":
80+
onlyAllowProxyConnections = Boolean.parseBoolean(value);
81+
break;
82+
case "debug-mode":
83+
debugMode = Boolean.parseBoolean(value);
84+
break;
85+
}
86+
}
87+
}
88+
89+
return new ConfigData(onlyAllowProxyConnections, debugMode);
90+
} catch (Exception e) {
91+
throw new TCPShieldInitializationException("Couldn't load config in config/tclshield.yml!");
92+
}
93+
}
94+
95+
private static class ConfigData {
96+
97+
protected boolean onlyAllowProxyConnections;
98+
protected boolean debugMode;
99+
100+
public ConfigData(boolean onlyAllowProxyConnections, boolean debugMode) {
101+
this.onlyAllowProxyConnections = onlyAllowProxyConnections;
102+
this.debugMode = debugMode;
103+
}
104+
}
105+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.tcpshield.tcpshield.fabric.impl;
2+
3+
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
4+
import net.tcpshield.tcpshield.abstraction.IPacket;
5+
import net.tcpshield.tcpshield.fabric.mixin.HandshakeC2SPacketAccessor;
6+
7+
public class FabricPacketImpl implements IPacket {
8+
9+
private final HandshakeC2SPacket handshake;
10+
11+
public FabricPacketImpl(HandshakeC2SPacket handshake) {
12+
this.handshake = handshake;
13+
}
14+
15+
@Override
16+
public String getRawPayload() {
17+
return ((HandshakeC2SPacketAccessor) handshake).getAddress();
18+
}
19+
20+
@Override
21+
public void modifyOriginalPacket(String hostname) throws Exception {
22+
// NO OPERATION
23+
}
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package net.tcpshield.tcpshield.fabric.impl;
2+
3+
import net.minecraft.network.ClientConnection;
4+
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
5+
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
6+
import net.minecraft.text.LiteralText;
7+
import net.tcpshield.tcpshield.abstraction.IPlayer;
8+
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
9+
import net.tcpshield.tcpshield.fabric.mixin.ClientConnectionAccessor;
10+
11+
import java.net.InetSocketAddress;
12+
13+
public class FabricPlayerImpl implements IPlayer {
14+
15+
private final ClientConnection connection;
16+
private String ip;
17+
18+
public FabricPlayerImpl(HandshakeC2SPacket packet, ClientConnection connection) {
19+
this.connection = connection;
20+
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
21+
}
22+
23+
@Override
24+
public String getIP() {
25+
return ip;
26+
}
27+
28+
@Override
29+
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
30+
// At this point, the IP/connection believe the player has the IP of TCPShield.
31+
// The ip passed into this method contains their CORRECT data, which we have to assign to the player network connection.
32+
((ClientConnectionAccessor) connection).setAddress(ip);
33+
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
34+
}
35+
36+
@Override
37+
public void disconnect() {
38+
connection.disconnect(new LiteralText("Connection failed. Please try again or contact an administrator."));
39+
}
40+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package net.tcpshield.tcpshield.fabric.mixin;
2+
3+
import io.netty.channel.Channel;
4+
import net.minecraft.network.ClientConnection;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.gen.Accessor;
7+
8+
import java.net.SocketAddress;
9+
10+
@Mixin(ClientConnection.class)
11+
public interface ClientConnectionAccessor {
12+
@Accessor
13+
void setAddress(SocketAddress address);
14+
15+
@Accessor
16+
Channel getChannel();
17+
18+
@Accessor
19+
SocketAddress getAddress();
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package net.tcpshield.tcpshield.fabric.mixin;
2+
3+
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.gen.Accessor;
6+
7+
@Mixin(HandshakeC2SPacket.class)
8+
public interface HandshakeC2SPacketAccessor {
9+
@Accessor
10+
String getAddress();
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package net.tcpshield.tcpshield.fabric.mixin;
2+
3+
import net.minecraft.network.ClientConnection;
4+
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
5+
import net.minecraft.server.network.ServerHandshakeNetworkHandler;
6+
import net.tcpshield.tcpshield.abstraction.IPacket;
7+
import net.tcpshield.tcpshield.abstraction.IPlayer;
8+
import net.tcpshield.tcpshield.fabric.TCPShieldFabric;
9+
import net.tcpshield.tcpshield.fabric.impl.FabricPacketImpl;
10+
import net.tcpshield.tcpshield.fabric.impl.FabricPlayerImpl;
11+
import org.spongepowered.asm.mixin.Final;
12+
import org.spongepowered.asm.mixin.Mixin;
13+
import org.spongepowered.asm.mixin.Shadow;
14+
import org.spongepowered.asm.mixin.injection.At;
15+
import org.spongepowered.asm.mixin.injection.Inject;
16+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
17+
18+
@Mixin(ServerHandshakeNetworkHandler.class)
19+
public class ServerHandshakeMixin {
20+
21+
@Shadow @Final private ClientConnection connection;
22+
23+
@Inject(
24+
method = "onHandshake",
25+
at = @At("HEAD"))
26+
private void onHandshake(HandshakeC2SPacket handshake, CallbackInfo ci) {
27+
IPacket packet = new FabricPacketImpl(handshake);
28+
IPlayer player = new FabricPlayerImpl(handshake, connection);
29+
30+
TCPShieldFabric.PACKET_HANDLER.onHandshake(packet, player);
31+
}
32+
}

0 commit comments

Comments
 (0)