diff --git a/build.gradle b/build.gradle index f827cdd..011bb63 100644 --- a/build.gradle +++ b/build.gradle @@ -21,12 +21,13 @@ buildscript { plugins { id 'java' id 'idea' + id 'fabric-loom' version '0.6-SNAPSHOT' } // -- // Variables // -- -version = '2.6.1' +version = '2.6.2' group = 'net.tcpshield.tcpshield' archivesBaseName = 'TCPShield' @@ -90,6 +91,9 @@ repositories { maven { url = "https://repo.opencollab.dev/maven-snapshots/" } + maven { + url = 'https://maven.fabricmc.net/' + } } dependencies { @@ -106,6 +110,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' @@ -128,8 +137,8 @@ configurations { } task updateVersion { - updateYamls() - updateJsons() +// updateYamls() +// updateJsons() } void updateYamls() { diff --git a/settings.gradle b/settings.gradle index cf3fb2c..c8a1a18 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,11 @@ -rootProject.name = 'TCPShield' +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} +rootProject.name = 'TCPShield' diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/TCPShieldFabric.java b/src/main/java/net/tcpshield/tcpshield/fabric/TCPShieldFabric.java new file mode 100644 index 0000000..c553e69 --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/TCPShieldFabric.java @@ -0,0 +1,54 @@ +package net.tcpshield.tcpshield.fabric; + +import net.fabricmc.api.DedicatedServerModInitializer; +import net.tcpshield.tcpshield.TCPShieldPacketHandler; +import net.tcpshield.tcpshield.TCPShieldPlugin; +import net.tcpshield.tcpshield.fabric.impl.FabricConfig; +import net.tcpshield.tcpshield.provider.ConfigProvider; +import net.tcpshield.tcpshield.util.Debugger; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.logging.Logger; + +public class TCPShieldFabric implements DedicatedServerModInitializer, TCPShieldPlugin { + + public static final Logger LOGGER = Logger.getLogger("TCPShield"); + public static TCPShieldPacketHandler packetHandler; + private FabricConfig config; + private Debugger debugger; + + @Override + public void onInitializeServer() { + try { + config = new FabricConfig(); + debugger = Debugger.createDebugger(this); + packetHandler = new TCPShieldPacketHandler(this); + } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException exception) { + exception.printStackTrace(); + } + + LOGGER.info("TCPShield has been loaded."); + } + + @Override + public ConfigProvider getConfigProvider() { + return config; + } + + @Override + public Logger getLogger() { + return LOGGER; + } + + @Override + public TCPShieldPacketHandler getPacketHandler() { + return packetHandler; + } + + @Override + public Debugger getDebugger() { + return debugger; + } +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricConfig.java b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricConfig.java new file mode 100644 index 0000000..c1eb296 --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricConfig.java @@ -0,0 +1,124 @@ +package net.tcpshield.tcpshield.fabric.impl; + +import net.fabricmc.loader.api.FabricLoader; +import net.tcpshield.tcpshield.fabric.TCPShieldFabric; +import net.tcpshield.tcpshield.provider.ConfigProvider; +import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException; +import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException; +import net.tcpshield.tcpshield.util.exception.config.ConfigResetException; +import net.tcpshield.tcpshield.util.exception.phase.ConfigException; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FabricConfig extends ConfigProvider { + + private final Map values = new HashMap<>(); + + public FabricConfig() { + this.dataFolder = new File(FabricLoader.getInstance().getConfigDir().toString()); + this.configFile = new File(dataFolder, "config.yml"); + + try { + reload(); + } catch (Exception e) { + throw new ConfigException(e); + } + } + + @Override + protected void checkNodes(String... nodes) throws ConfigException { + for (String node : nodes) { + if(!values.containsKey(node)) + throw new ConfigException("The node \"" + node + "\" does not exist in the config."); + } + } + + @Override + protected void reset() throws ConfigResetException { + try { + values.clear(); + + try { + configFile.delete(); + } catch (Exception ignored) { + // Just ignore since it either does not exist, or we can overwrite + } + + // 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, configFile.toPath()); + } + } catch (Exception e) { + throw new ConfigResetException(e); + } + } + + @Override + protected void load() throws ConfigLoadException { + try { + values.clear(); + values.putAll(loadConfig()); + + checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode", "enable-geyser-support", "prefer-protocollib"); + + this.onlyProxy = Boolean.parseBoolean(values.get("only-allow-proxy-connections")); + this.timestampValidationMode = values.get("timestamp-validation"); + this.doDebug = Boolean.parseBoolean(values.get("debug-mode")); + this.geyser = Boolean.parseBoolean(values.get("enable-geyser-support")); + this.preferProtocolLib = Boolean.parseBoolean(values.get("prefer-protocollib")); + } catch (Exception e) { + throw new ConfigLoadException(e); + } + } + + @Override + public void reload() throws ConfigReloadException { + try { + if(!dataFolder.exists()) + dataFolder.mkdir(); + + if(!configFile.exists()) + reset(); + + try { + load(); + } catch (ConfigLoadException exception) { + TCPShieldFabric.LOGGER.warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)"); + reset(); + reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails + } + } catch (Exception e) { + throw new ConfigReloadException(e); + } + } + + private Map loadConfig() { + Map configValues = new HashMap<>(); + + try { + List strings = Files.readAllLines(configFile.toPath()); + + // Rudimentary config parsing + for (String line : strings) { + String[] entry = line.replace(" ", "").split(":"); + + // A config option will only be valid if it is in the format 'a: b'. + // Ensure that is the case now. + if (entry.length == 2) { + String key = entry[0]; + String value = entry[1]; + configValues.put(key, value); + } + } + } catch (Exception e) { + throw new ConfigException("Couldn't load config at config/config.yml!"); + } + + return configValues; + } +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPacket.java b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPacket.java new file mode 100644 index 0000000..c910fb0 --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPacket.java @@ -0,0 +1,25 @@ +package net.tcpshield.tcpshield.fabric.impl; + +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.tcpshield.tcpshield.fabric.mixin.HandshakeC2SPacketAccessor; +import net.tcpshield.tcpshield.provider.PacketProvider; +import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException; + +public class FabricPacket implements PacketProvider { + + private final HandshakeC2SPacket handshake; + + public FabricPacket(HandshakeC2SPacket handshake) { + this.handshake = handshake; + } + + @Override + public String getPayloadString() { + return ((HandshakeC2SPacketAccessor) handshake).getAddress(); + } + + @Override + public void setPacketHostname(String hostname) throws PacketManipulationException { + // NO OPERATION + } +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPlayer.java b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPlayer.java new file mode 100644 index 0000000..7e3f747 --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/impl/FabricPlayer.java @@ -0,0 +1,48 @@ +package net.tcpshield.tcpshield.fabric.impl; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.text.LiteralText; +import net.tcpshield.tcpshield.fabric.mixin.ClientConnectionAccessor; +import net.tcpshield.tcpshield.provider.PlayerProvider; + +import java.net.InetSocketAddress; + +public class FabricPlayer implements PlayerProvider { + + private final ClientConnection connection; + private String ip; + + public FabricPlayer(HandshakeC2SPacket packet, ClientConnection connection) { + this.connection = connection; + this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress(); + } + + @Override + public String getUUID() { + return "unknown"; + } + + @Override + public String getName() { + return "unknown"; + } + + @Override + public String getIP() { + return ip; + } + + @Override + public void setIP(InetSocketAddress ip) { + // 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.")); + } +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ClientConnectionAccessor.java b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ClientConnectionAccessor.java new file mode 100644 index 0000000..c43868f --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ClientConnectionAccessor.java @@ -0,0 +1,18 @@ +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(); +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/mixin/HandshakeC2SPacketAccessor.java b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/HandshakeC2SPacketAccessor.java new file mode 100644 index 0000000..fe7e6fa --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/HandshakeC2SPacketAccessor.java @@ -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(); +} diff --git a/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ServerHandshakeMixin.java b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ServerHandshakeMixin.java new file mode 100644 index 0000000..10f1b25 --- /dev/null +++ b/src/main/java/net/tcpshield/tcpshield/fabric/mixin/ServerHandshakeMixin.java @@ -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.fabric.TCPShieldFabric; +import net.tcpshield.tcpshield.fabric.impl.FabricPacket; +import net.tcpshield.tcpshield.fabric.impl.FabricPlayer; +import net.tcpshield.tcpshield.provider.PacketProvider; +import net.tcpshield.tcpshield.provider.PlayerProvider; +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) { + PacketProvider packet = new FabricPacket(handshake); + PlayerProvider player = new FabricPlayer(handshake, connection); + + TCPShieldFabric.packetHandler.handleHandshake(packet, player); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..ee8bf75 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 1, + "id": "tcp-shield", + "version": "2.5", + "name": "TCPShield", + "description": "TCPShield support for Fabric", + "authors": [ + "TCPShield" + ], + "contact": { + "homepage": "https://tcpshield.com" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "server": [ + "net.tcpshield.tcpshield.fabric.TCPShieldFabric" + ] + }, + "mixins": [ + "tcpshield.mixins.json" + ], + "depends": { + "fabricloader": "*", + "minecraft": "*" + } +} \ No newline at end of file diff --git a/src/main/resources/tcpshield.mixins.json b/src/main/resources/tcpshield.mixins.json new file mode 100644 index 0000000..51d8b77 --- /dev/null +++ b/src/main/resources/tcpshield.mixins.json @@ -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 + } +} \ No newline at end of file