From 772b6c2501010c50e985a324b908dfaf0938d007 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Thu, 23 Nov 2023 18:07:09 +0100 Subject: [PATCH] Update 1.2.26 --- README.md | 7 +- build.gradle | 85 ++++----------- .../mixins/MixinEntityClientPlayerMP.java | 5 + .../client/mixins/MixinEntityPlayerSP.java | 16 ++- .../client/mixins/MixinGuiIngameMenu.java | 15 ++- .../client/mixins/MixinGuiMainMenu.java | 13 +++ .../foxloader/client/mixins/MixinWorld.java | 9 ++ .../foxloader/loader/ClientModLoader.java | 5 +- .../resources/foxloader.client.mixins.json | 1 + common/generate.gradle | 4 + .../foxloader/launcher/DependencyHelper.java | 42 ++++++- .../foxloader/launcher/utils/IOUtils.java | 40 ++++++- .../foxloader/launcher/utils/NetUtils.java | 103 +++++++++++------- .../com/fox2code/foxloader/loader/Mod.java | 10 ++ .../foxloader/loader/ModContainer.java | 13 +++ .../fox2code/foxloader/loader/ModLoader.java | 39 +++++++ .../foxloader/network/NetworkPlayer.java | 8 ++ .../foxloader/registry/RegisteredWorld.java | 7 ++ .../foxloader/dev/GradlePlugin.groovy | 16 ++- gradle.properties | 3 +- .../server/mixins/MixinEntityPlayerMP.java | 7 ++ .../server/mixins/MixinNetServerHandler.java | 25 +++++ .../foxloader/server/mixins/MixinWorld.java | 7 ++ 23 files changed, 359 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index 7098019..fd4bb6b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ModLoader for Minecraft ReIndev +[![](https://www.jitpack.io/v/com.fox2code/FoxLoader.svg)](https://www.jitpack.io/#com.fox2code/FoxLoader) + ## Community You can [join the official ReIndev Discord here](https://discord.gg/38Vfes6NpR) @@ -11,7 +13,7 @@ A feature missing to make your mod? [Just open an issue!](https://github.com/Fox ## Installation For client side installation, just run the jar file. -Either by double clicking on it, or running `java -jar FoxLoader.jar` +Either by double-clicking on it, or running `java -jar FoxLoader.jar` To run FoxLoader as a server just run `java -jar FoxLoader.jar --server` @@ -37,6 +39,7 @@ For example mod check here: https://github.com/Fox2Code/FoxLoaderExampleMod - Load core-mods (Aka. Legacy mods) into class loader - Load pre-patches (FoxLoader asm patches) - Load mods into class loader +- Do full game pre-patching - Initialize mixins - Allow game to be loaded - Load mods @@ -46,7 +49,7 @@ As you see the game is allowed to be loaded very late into the boot process, eve This ensures that all patches introduced by mods are applied, but also prevent code loaded in MixinPlugins to load Minecraft classes, -please keep that in mind when messing with mixins. +please keep that in mind when messing with mixins plugins. ## Lua mods diff --git a/build.gradle b/build.gradle index 61422b8..bcfc35c 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ subprojects { } withSourcesJar() + withJavadocJar() } test { @@ -86,8 +87,18 @@ subprojects { // We need to download client and server to be able to compile the project. // Let's do that there really quick. -static void download(URL url, File file) { +static void downloadIfNeeded(URL url, File file, String hash) { file.getParentFile().mkdirs() + if (file.exists()) { + byte[] localData = Files.readAllBytes(file.toPath()) + byte[] localHash = MessageDigest.getInstance("SHA-256").digest(localData) + String hashString = new BigInteger(1, localHash).toString(16) + if (hashString == hash) return + if (!file.delete()) { + throw new IOException("Failed to delete corrupted file: " + file.getName()) + } + } + println("Downloading " + url) HttpURLConnection connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY) String javaVendor = System.getProperty("java.vendor") String javaVersion = System.getProperty("java.version") @@ -102,72 +113,20 @@ static void download(URL url, File file) { file.withOutputStream { fileOut -> fileOut << connection.getInputStream() } + byte[] fileData = Files.readAllBytes(file.toPath()) + byte[] fileHash = MessageDigest.getInstance("SHA-256").digest(fileData) + String hashString = new BigInteger(1, fileHash).toString(16) + if (hash != hashString) { + throw new IOException("Excpeted HASH: " + hash + " got " + hashString) + } } File clientFile = new File(project.rootDir, "client" + File.separator + "libs" + File.separator + project['reindev.clientJar']) - -if (!clientFile.exists()) { - println("Downloading client from " + project['reindev.clientUrl']) - - for (int i = 0; i < 2; i++) { // Max 1 retry for downloading - if (i > 0) { - println("Retrying download in 1 second...") - Thread.sleep(1000); - } - - download(new URL(project['reindev.clientUrl'] as String), clientFile) - - byte[] data = Files.readAllBytes(clientFile.toPath()) - byte[] hash = MessageDigest.getInstance("SHA-256").digest(data) - String hashString = new BigInteger(1, hash).toString(16) - - println("SHA256 hash: " + hashString) - if (hashString == project['reindev.clientSHA256Sum'] as String) { - println("Hash matched expected") - break - } else { - println("Hash did not match! Expected: " + project['reindev.clientSHA256Sum']) - if (clientFile.delete()) { - println("Client jar file deleted") - } else { - println("Failed to delete client jar file") - break; - } - } - } -} - File serverFile = new File(project.rootDir, "server" + File.separator + "libs" + File.separator + project['reindev.serverJar']) -if (!serverFile.exists()) { - println("\nDownloading server from " + project['reindev.serverUrl']) - - for (int i = 0; i < 2; i++) { // Max 1 retry for downloading - if (i > 0) { - println("Retrying download in 1 second...") - Thread.sleep(1000); - } - - download(new URL(project['reindev.serverUrl'] as String), serverFile) - - byte[] data = Files.readAllBytes(serverFile.toPath()) - byte[] hash = MessageDigest.getInstance("SHA-256").digest(data) - String hashString = new BigInteger(1, hash).toString(16) - - println("SHA256 hash: " + hashString) - if (hashString == project['reindev.serverSHA256Sum'] as String) { - println("Hash matched expected") - break - } else { - println("Hash did not match! Expected: " + project['reindev.serverSHA256Sum']) - if (serverFile.delete()) { - println("Server file deleted") - } else { - println("Failed to delete server file") - break; - } - } - } -} +downloadIfNeeded(new URL(project['reindev.clientUrl'] as String), clientFile, + project['reindev.clientSHA256Sum'] as String) +downloadIfNeeded(new URL(project['reindev.serverUrl'] as String), serverFile, + project['reindev.serverSHA256Sum'] as String) diff --git a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityClientPlayerMP.java b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityClientPlayerMP.java index ac36f22..a005860 100644 --- a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityClientPlayerMP.java +++ b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityClientPlayerMP.java @@ -72,4 +72,9 @@ public boolean isConnected() { return sendQueue != null && Minecraft.getInstance().isMultiplayerWorld() && !((NetClientHandlerExtensions) sendQueue).isDisconnected(); } + + @Override + public void sendPlayerThroughPortalRegistered() { + throw new RuntimeException("No authority over player"); + } } diff --git a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityPlayerSP.java b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityPlayerSP.java index b2ff5a2..7b68ec3 100644 --- a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityPlayerSP.java +++ b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinEntityPlayerSP.java @@ -8,10 +8,18 @@ import net.minecraft.src.client.player.EntityPlayerSP; import net.minecraft.src.game.entity.Entity; import net.minecraft.src.game.entity.player.EntityPlayer; +import net.minecraft.src.game.level.World; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; @Mixin(EntityPlayerSP.class) -public abstract class MixinEntityPlayerSP implements NetworkPlayer { +public abstract class MixinEntityPlayerSP extends EntityPlayer implements NetworkPlayer { + + @Shadow public Minecraft mc; + + public MixinEntityPlayerSP(World var1) { + super(var1); + } @Override public ConnectionType getConnectionType() { @@ -69,4 +77,10 @@ public RegisteredItemStack getRegisteredHeldItem() { EntityPlayerSP networkPlayerSP = (EntityPlayerSP) (Object) this; return ClientMod.toRegisteredItemStack(networkPlayerSP.inventory.getCurrentItem()); } + + @Override + public void sendPlayerThroughPortalRegistered() { + this.mc.usePortal(); + this.inPortal = false; + } } diff --git a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiIngameMenu.java b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiIngameMenu.java index fb1ac04..9634e57 100644 --- a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiIngameMenu.java +++ b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiIngameMenu.java @@ -2,6 +2,9 @@ import com.fox2code.foxloader.client.gui.GuiModList; import com.fox2code.foxloader.client.gui.GuiUpdateButton; +import com.fox2code.foxloader.loader.ModLoader; +import com.fox2code.foxloader.network.NetworkPlayer; +import net.minecraft.client.Minecraft; import net.minecraft.src.client.gui.GuiButton; import net.minecraft.src.client.gui.GuiIngameMenu; import net.minecraft.src.client.gui.GuiScreen; @@ -18,8 +21,16 @@ public void onInitGui(CallbackInfo ci) { } @Inject(method = "actionPerformed", at = @At(value = "HEAD"), cancellable = true) - public void onActionPerformed(GuiButton var1, CallbackInfo ci) { - if (var1.id == 500) { + public void onActionPerformed(GuiButton button, CallbackInfo ci) { + if (button.id == 1) { + NetworkPlayer networkPlayer = (NetworkPlayer) Minecraft.getInstance().thePlayer; + if (networkPlayer != null && networkPlayer.getConnectionType() == + NetworkPlayer.ConnectionType.SINGLE_PLAYER) { + ModLoader.Internal.notifyNetworkPlayerDisconnected(networkPlayer, null); + } + } + + if (button.id == 500) { this.mc.displayGuiScreen(new GuiModList(this)); ci.cancel(); } diff --git a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiMainMenu.java b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiMainMenu.java index 894758a..c6911bf 100644 --- a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiMainMenu.java +++ b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinGuiMainMenu.java @@ -4,10 +4,13 @@ import com.fox2code.foxloader.client.gui.GuiUpdateButton; import com.fox2code.foxloader.launcher.BuildConfig; import com.fox2code.foxloader.loader.ModLoader; +import com.fox2code.foxloader.network.ChatColors; +import net.minecraft.src.client.Session; import net.minecraft.src.client.gui.*; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GuiMainMenu.class) @@ -25,6 +28,16 @@ public void onActionPerformed(GuiButton var1, CallbackInfo ci) { } } + @Redirect(method = "drawScreen", at = @At(value = "FIELD", target = + "Lnet/minecraft/src/client/Session;username:Ljava/lang/String;")) + public String onGetUsername(Session instance) { + final String username = instance.username; + if (ModLoader.Contributors.hasContributorName(username)) { + return ChatColors.RAINBOW + username; + } + return username; + } + @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/src/client/gui/GuiScreen;drawScreen(IIF)V")) public void onDrawGuiScreen(int n, int n2, float deltaTicks, CallbackInfo ci) { diff --git a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinWorld.java b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinWorld.java index fbbd3c5..d356796 100644 --- a/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinWorld.java +++ b/client/src/main/java/com/fox2code/foxloader/client/mixins/MixinWorld.java @@ -8,6 +8,8 @@ import net.minecraft.src.game.entity.other.EntityItem; import net.minecraft.src.game.entity.player.EntityPlayer; import net.minecraft.src.game.level.World; +import net.minecraft.src.game.level.WorldProvider; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -25,6 +27,8 @@ public abstract class MixinWorld implements RegisteredWorld { @Shadow public abstract boolean setBlockAndMetadataWithNotify(int xCoord, int yCoord, int zCoord, int block, int metadata); @Shadow public abstract boolean entityJoinedWorld(Entity entity); + @Shadow @Final public WorldProvider worldProvider; + @Override public boolean hasRegisteredControl() { return !this.multiplayerWorld; @@ -80,4 +84,9 @@ public List getRegisteredTileEntities() { public List getRegisteredNetworkPlayers() { return (List) (Object) this.playerEntities; } + + @Override + public int getRegisteredDimensionID() { + return this.worldProvider.worldType; + } } diff --git a/client/src/main/java/com/fox2code/foxloader/loader/ClientModLoader.java b/client/src/main/java/com/fox2code/foxloader/loader/ClientModLoader.java index ec752e2..ed7cf51 100644 --- a/client/src/main/java/com/fox2code/foxloader/loader/ClientModLoader.java +++ b/client/src/main/java/com/fox2code/foxloader/loader/ClientModLoader.java @@ -3,6 +3,7 @@ import com.fox2code.foxloader.launcher.BuildConfig; import com.fox2code.foxloader.launcher.FoxLauncher; import com.fox2code.foxloader.launcher.LauncherType; +import com.fox2code.foxloader.launcher.utils.IOUtils; import com.fox2code.foxloader.launcher.utils.NetUtils; import com.fox2code.foxloader.launcher.utils.Platform; import com.fox2code.foxloader.launcher.utils.SourceUtil; @@ -46,14 +47,14 @@ private static void computeClientHello() { new ArrayList<>(ModLoader.modContainers.size() + ModLoader.coreMods.size()); for (File coreMod : ModLoader.coreMods) { - byte[] sha256 = NetUtils.hashOf(coreMod); + byte[] sha256 = IOUtils.sha256Of(coreMod); clientModData.add(new ClientHello.ClientModData( coreMod.getName(), sha256, "", "")); } for (ModContainer modContainer : ModLoader.modContainers.values()) { byte[] sha256 = nullSHA256; if (modContainer.file != null) { - sha256 = NetUtils.hashOf(modContainer.file); + sha256 = IOUtils.sha256Of(modContainer.file); } clientModData.add(new ClientHello.ClientModData( modContainer.id, sha256, diff --git a/client/src/main/resources/foxloader.client.mixins.json b/client/src/main/resources/foxloader.client.mixins.json index 0adacef..ecf6628 100644 --- a/client/src/main/resources/foxloader.client.mixins.json +++ b/client/src/main/resources/foxloader.client.mixins.json @@ -15,6 +15,7 @@ "MixinEntityClientPlayerMP", "MixinEntityItem", "MixinEntityLiving", + "MixinEntityPlayer", "MixinEntityPlayerSP", "MixinEntityRenderer", "MixinGameSettings", diff --git a/common/generate.gradle b/common/generate.gradle index e90b9db..7c7c9c8 100644 --- a/common/generate.gradle +++ b/common/generate.gradle @@ -35,6 +35,8 @@ static void generateBuildConfig0(File buildConfigSrc, Project project) { final String REINDEV_VERSION = project['reindev.version'] final String CLIENT_URL = project['reindev.clientUrl'] final String SERVER_URL = project['reindev.serverUrl'] + final String CLIENT_SHA256_SUM = project['reindev.clientSHA256Sum'] + final String SERVER_SHA256_SUM = project['reindev.serverSHA256Sum'] FileOutputStream fileOutputStream = new FileOutputStream(buildConfigSrc) try { PrintStream printStream = new PrintStream(fileOutputStream) @@ -52,6 +54,8 @@ static void generateBuildConfig0(File buildConfigSrc, Project project) { printStream.println(" public static final String REINDEV_VERSION = \"" + REINDEV_VERSION + "\";") printStream.println(" public static final String CLIENT_URL = \"" + CLIENT_URL + "\";") printStream.println(" public static final String SERVER_URL = \"" + SERVER_URL + "\";") + printStream.println(" public static final String CLIENT_SHA256_SUM = \"" + CLIENT_SHA256_SUM + "\";") + printStream.println(" public static final String SERVER_SHA256_SUM = \"" + SERVER_SHA256_SUM + "\";") printStream.println("}") } finally { fileOutputStream.close() diff --git a/common/src/main/java/com/fox2code/foxloader/launcher/DependencyHelper.java b/common/src/main/java/com/fox2code/foxloader/launcher/DependencyHelper.java index 846f32d..b3d9c55 100644 --- a/common/src/main/java/com/fox2code/foxloader/launcher/DependencyHelper.java +++ b/common/src/main/java/com/fox2code/foxloader/launcher/DependencyHelper.java @@ -1,13 +1,16 @@ package com.fox2code.foxloader.launcher; +import com.fox2code.foxloader.launcher.utils.IOUtils; import com.fox2code.foxloader.launcher.utils.NetUtils; import com.fox2code.foxloader.launcher.utils.Platform; import java.io.*; import java.lang.instrument.Instrumentation; import java.lang.reflect.Method; +import java.math.BigInteger; import java.net.*; import java.nio.file.Files; +import java.security.NoSuchAlgorithmException; import java.util.jar.JarFile; public class DependencyHelper { @@ -42,11 +45,13 @@ GSON_DEPENDENCY, new Dependency("com.google.guava:guava:21.0", MAVEN_CENTRAL, "c public static final Dependency[] clientDependencies = new Dependency[]{ new Dependency("net.silveros:reindev:" + BuildConfig.REINDEV_VERSION, - BuildConfig.CLIENT_URL, "net.minecraft.src.client.MinecraftImpl") + BuildConfig.CLIENT_URL, "net.minecraft.src.client.MinecraftImpl", + null, BuildConfig.CLIENT_SHA256_SUM) }; public static final Dependency[] serverDependencies = new Dependency[]{ new Dependency("net.silveros:reindev-server:" + BuildConfig.REINDEV_VERSION, - BuildConfig.SERVER_URL, "net.minecraft.server.MinecraftServer") + BuildConfig.SERVER_URL, "net.minecraft.server.MinecraftServer", + null, BuildConfig.SERVER_SHA256_SUM) }; private static File mcLibraries; @@ -139,6 +144,7 @@ private static File loadDependencyImpl(Dependency dependency, boolean minecraft, String postURL = resolvePostURL(dependency.name); File file = new File(mcLibraries, fixUpPath(postURL)); boolean justDownloaded = false; + checkHashOrDelete(file, dependency, false); if (!file.exists()) { File parentFile = file.getParentFile(); if (!parentFile.isDirectory() && !parentFile.mkdirs()) { @@ -167,6 +173,7 @@ private static File loadDependencyImpl(Dependency dependency, boolean minecraft, } } } + checkHashOrDelete(file, dependency, true); if (dev) return file; // We don't have a FoxClass loader in dev environment. try { if (minecraft) { @@ -193,6 +200,28 @@ private static File loadDependencyImpl(Dependency dependency, boolean minecraft, return file; } + private static void checkHashOrDelete(File file, Dependency dependency, boolean errorOut) { + if (dependency.sha256Sum == null || !file.exists()) return; + String hashString; + try { + hashString = new BigInteger(1, IOUtils.sha256Of(file)).toString(16); + } catch (IOException e) { + hashString = ""; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + if (!dependency.sha256Sum.equals(hashString)) { + boolean deleteSuccessful = file.delete(); + if (errorOut) { + throw new RuntimeException("Remote dependency " + dependency.name + " checksum mismatch " + + "(got: " + hashString + ", expected: " + dependency.sha256Sum + ")"); + } + if (!deleteSuccessful) { + throw new RuntimeException("Can't delete dependency with checksum mismatch " + dependency.name); + } + } + } + public static class Agent { private static Instrumentation inst = null; @@ -302,17 +331,22 @@ private static String resolvePostURL(String string) { } public static class Dependency { - public final String name, repository, classCheck, fallbackUrl; + public final String name, repository, classCheck, fallbackUrl, sha256Sum; public Dependency(String name, String repository, String classCheck) { - this(name, repository, classCheck, null); + this(name, repository, classCheck, null, null); } public Dependency(String name, String repository, String classCheck, String fallbackUrl) { + this(name, repository, classCheck, fallbackUrl, null); + } + + public Dependency(String name, String repository, String classCheck, String fallbackUrl, String sha256Sum) { this.name = name; this.repository = repository; this.classCheck = classCheck; this.fallbackUrl = fallbackUrl; + this.sha256Sum = sha256Sum; } } } diff --git a/common/src/main/java/com/fox2code/foxloader/launcher/utils/IOUtils.java b/common/src/main/java/com/fox2code/foxloader/launcher/utils/IOUtils.java index d7e79db..75aa699 100644 --- a/common/src/main/java/com/fox2code/foxloader/launcher/utils/IOUtils.java +++ b/common/src/main/java/com/fox2code/foxloader/launcher/utils/IOUtils.java @@ -1,8 +1,10 @@ package com.fox2code.foxloader.launcher.utils; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; public class IOUtils { public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException { @@ -25,4 +27,36 @@ public static void copyAndClose(InputStream inputStream, OutputStream outputStre } } } + + public static byte[] sha256Of(File file) throws IOException, NoSuchAlgorithmException { + byte[] buffer= new byte[8192]; + int count; + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()))) { + while ((count = bis.read(buffer)) > 0) { + digest.update(buffer, 0, count); + } + } + + byte[] hash = digest.digest(); + if (hash.length != 32) { + throw new AssertionError( + "Result hash is not the result hash of a SHA-256 hash " + + "(got " + hash.length + ", expected 32)"); + } + return hash; + } + + public static byte[] sha256Of(String text) throws IOException, NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(text.getBytes(StandardCharsets.UTF_8)); + + byte[] hash = digest.digest(); + if (hash.length != 32) { + throw new AssertionError( + "Result hash is not the result hash of a SHA-256 hash " + + "(got " + hash.length + ", expected 32)"); + } + return hash; + } } diff --git a/common/src/main/java/com/fox2code/foxloader/launcher/utils/NetUtils.java b/common/src/main/java/com/fox2code/foxloader/launcher/utils/NetUtils.java index b6f49e6..5a7330a 100644 --- a/common/src/main/java/com/fox2code/foxloader/launcher/utils/NetUtils.java +++ b/common/src/main/java/com/fox2code/foxloader/launcher/utils/NetUtils.java @@ -2,9 +2,8 @@ import java.io.*; import java.net.*; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Base64; @@ -12,6 +11,7 @@ public class NetUtils { private static final String GRADLE_USER_AGENT; + private static final Charset DEFAULT_ENCODING; static { String javaVendor = System.getProperty("java.vendor"); @@ -20,8 +20,9 @@ public class NetUtils { String osName = System.getProperty("os.name"); String osVersion = System.getProperty("os.version"); String osArch = System.getProperty("os.arch"); - GRADLE_USER_AGENT = String.format("Gradle/7.5.1 (%s;%s;%s) (%s;%s;%s)", + GRADLE_USER_AGENT = String.format("Gradle/8.4 (%s;%s;%s) (%s;%s;%s)", osName, osVersion, osArch, javaVendor, javaVersion, javaVendorVersion); + DEFAULT_ENCODING = StandardCharsets.UTF_8; } public static boolean isValidURL(String url) { @@ -33,36 +34,14 @@ public static boolean isValidURL(String url) { } } + @Deprecated public static byte[] hashOf(File file) throws IOException, NoSuchAlgorithmException { - byte[] buffer= new byte[8192]; - int count; - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()))) { - while ((count = bis.read(buffer)) > 0) { - digest.update(buffer, 0, count); - } - } - - byte[] hash = digest.digest(); - if (hash.length != 32) { - throw new AssertionError( - "Result hash is not the result hash of a SHA-256 hash " + - "(got " + hash.length + ", expected 32)"); - } - return hash; + return IOUtils.sha256Of(file); } + @Deprecated public static byte[] hashOf(String text) throws IOException, NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(text.getBytes(StandardCharsets.UTF_8)); - - byte[] hash = digest.digest(); - if (hash.length != 32) { - throw new AssertionError( - "Result hash is not the result hash of a SHA-256 hash " + - "(got " + hash.length + ", expected 32)"); - } - return hash; + return IOUtils.sha256Of(text); } public static void downloadTo(String url, OutputStream outputStream) throws IOException { @@ -70,13 +49,27 @@ public static void downloadTo(String url, OutputStream outputStream) throws IOEx } public static void downloadTo(URL url, OutputStream outputStream) throws IOException { + downloadToImpl(url, outputStream, false); + } + + public static String downloadAsString(String url) throws IOException { + return downloadAsString(URI.create(url).toURL()); + } + + public static String downloadAsString(URL url) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Charset charset = downloadToImpl(url, byteArrayOutputStream, true); + return new String(byteArrayOutputStream.toByteArray(), charset); + } + + private static Charset downloadToImpl(URL url, OutputStream outputStream, boolean findCharset) throws IOException { if (BrowserLike.DESKTOP_MODE_DOMAINS.contains(url.getHost())) { // Good practice is to always say when we are a bot, unless... - BrowserLike.downloadTo(url, outputStream); - return; + return BrowserLike.downloadToImpl(url, outputStream, findCharset); } HttpURLConnection con = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); con.setConnectTimeout(5000); + con.setInstanceFollowRedirects(true); con.setRequestProperty("Connection", "keep-alive"); con.setRequestProperty("User-Agent", GRADLE_USER_AGENT); try (InputStream is = con.getInputStream()) { @@ -89,16 +82,42 @@ public static void downloadTo(URL url, OutputStream outputStream) throws IOExcep outputStream.flush(); } + return findCharset ? charsetFromContentTypeImpl(con.getContentType(), true) : DEFAULT_ENCODING; } - public static String downloadAsString(String url) throws IOException { - return downloadAsString(URI.create(url).toURL()); - } - - public static String downloadAsString(URL url) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - downloadTo(url, byteArrayOutputStream); - return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8); + private static Charset charsetFromContentTypeImpl(String contentType, boolean allowJank) { + if (contentType == null || contentType.isEmpty()) + return DEFAULT_ENCODING; + int start = contentType.indexOf(";charset="); + if (start != -1) { + start += 9; + } else { + start = contentType.indexOf("; charset="); + if (start == -1) return DEFAULT_ENCODING; + start += 10; + } + start += 9; + int end = contentType.indexOf(';', start); + if (end == -1) end = contentType.length(); + try { + if (contentType.charAt(start) == ' ') + start++; + String charset; + if (contentType.charAt(start) == '"') { + start++; + if (allowJank) { + charset = contentType.substring(start, end); + if (charset.contains("\\\"")) + return DEFAULT_ENCODING; + charset = charset.replace("\"", ""); + } else if (contentType.charAt(end - 1) == '"') { + end--; + charset = contentType.substring(start, end); + } else return DEFAULT_ENCODING; + } else charset = contentType.substring(start, end); + return Charset.forName(charset); + } catch (Exception ignored) {} + return DEFAULT_ENCODING; } private static class BrowserLike { @@ -109,7 +128,7 @@ private static class BrowserLike { new String(Base64.getDecoder().decode("bWVkaWEuZGlzY29yZGFwcC5uZXQ="), StandardCharsets.UTF_8) )); - static void downloadTo(URL url, OutputStream outputStream) throws IOException { + static Charset downloadToImpl(URL url, OutputStream outputStream, boolean findCharset) throws IOException { HttpURLConnection con = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); con.setRequestMethod("GET"); @@ -118,7 +137,9 @@ static void downloadTo(URL url, OutputStream outputStream) throws IOException { con.setDoInput(true); con.setDoOutput(true); con.setUseCaches(false); + con.setRequestProperty("Connection", "keep-alive"); con.setRequestProperty("User-Agent", DESKTOP_USER_AGENT); + con.setRequestProperty("Upgrade-Insecure-Requests", "1"); int http = con.getResponseCode(); @@ -133,6 +154,8 @@ static void downloadTo(URL url, OutputStream outputStream) throws IOException { outputStream.flush(); } + + return findCharset ? charsetFromContentTypeImpl(con.getContentType(), false) : DEFAULT_ENCODING; } } } diff --git a/common/src/main/java/com/fox2code/foxloader/loader/Mod.java b/common/src/main/java/com/fox2code/foxloader/loader/Mod.java index beac0be..9002aab 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/Mod.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/Mod.java @@ -171,6 +171,16 @@ public boolean onPlayerAttackEntity(NetworkPlayer networkPlayer, RegisteredItemS return false; } + /** + * Executed before player data is saved, on single player, return value is ignored. + * + * @param kickMessage if non-null, the player was kicked instead of disconnecting. + * + * @return true to cancel the game sending the default "player has left the game message" + * */ + public boolean onNetworkPlayerDisconnected(NetworkPlayer networkPlayer, String kickMessage, boolean cancelled) { + return false; + } // GameRegistry mirror diff --git a/common/src/main/java/com/fox2code/foxloader/loader/ModContainer.java b/common/src/main/java/com/fox2code/foxloader/loader/ModContainer.java index 59ada4e..92a3f54 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/ModContainer.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/ModContainer.java @@ -376,4 +376,17 @@ boolean notifyPlayerAttackEntity(NetworkPlayer networkPlayer, RegisteredItemStac networkPlayer, itemStack, targetEntity, cancelled); return cancelled; } + + boolean notifyNetworkPlayerDisconnected(NetworkPlayer networkPlayer, String kickMessage, boolean cancelled) { + if (commonMod != null) + cancelled |= commonMod.onNetworkPlayerDisconnected( + networkPlayer, kickMessage, cancelled); + if (clientMod != null) + cancelled |= clientMod.onNetworkPlayerDisconnected( + networkPlayer, kickMessage, cancelled); + if (serverMod != null) + cancelled |= serverMod.onNetworkPlayerDisconnected( + networkPlayer, kickMessage, cancelled); + return cancelled; + } } diff --git a/common/src/main/java/com/fox2code/foxloader/loader/ModLoader.java b/common/src/main/java/com/fox2code/foxloader/loader/ModLoader.java index 55db4ed..b29d842 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/ModLoader.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/ModLoader.java @@ -470,5 +470,44 @@ public static boolean notifyPlayerAttackEntity( } return cancelled; } + + public static boolean notifyNetworkPlayerDisconnected( + NetworkPlayer networkPlayer, String kickMessage) { + boolean cancelled = false; + for (ModContainer modContainer : modContainers.values()) { + cancelled = modContainer.notifyNetworkPlayerDisconnected( + networkPlayer, kickMessage, cancelled); + } + return cancelled; + } + } + + public static class Contributors { + private static final HashSet contributorsUUIDs = new HashSet<>(); + private static final HashSet contributorsNames = new HashSet<>(); + + static { + // If your name is not there, and you contributed, just open an issue on GitHub + addContributor("a5adabf9-0c1f-4d03-855b-61e334cd96d7", "Fox2Code"); + addContributor("76982056-c381-46f6-ab25-2415e1e4d554", "kivattt"); + addContributor("898febf0-4bd0-4a77-892c-2b1cbf534830", "_Dereku"); + } + + private static void addContributor(String uuid, String name) { + contributorsUUIDs.add(uuid); + contributorsNames.add(name.toLowerCase(Locale.ROOT)); + } + + public static boolean hasContributorUUID(UUID uuid) { + return contributorsUUIDs.contains(uuid.toString()); + } + + public static boolean hasContributorUUID(String uuid) { + return contributorsUUIDs.contains(uuid); + } + + public static boolean hasContributorName(String name) { + return contributorsNames.contains(name.toLowerCase(Locale.ROOT)); + } } } diff --git a/common/src/main/java/com/fox2code/foxloader/network/NetworkPlayer.java b/common/src/main/java/com/fox2code/foxloader/network/NetworkPlayer.java index 97cc167..251b1d1 100644 --- a/common/src/main/java/com/fox2code/foxloader/network/NetworkPlayer.java +++ b/common/src/main/java/com/fox2code/foxloader/network/NetworkPlayer.java @@ -55,6 +55,14 @@ public interface NetworkPlayer extends RegisteredEntityLiving, RegisteredCommand */ default RegisteredItemStack getRegisteredHeldItem() { throw new RuntimeException(); } + /** + * Call code to switch player between nether and overworld, if + * the player is in neither, it will send the player to the nether. + *
+ * This will also make a new nether portal if needed + */ + default void sendPlayerThroughPortalRegistered() { throw new RuntimeException(); } + enum ConnectionType { SINGLE_PLAYER(true, true), CLIENT_ONLY(true, false), SERVER_ONLY(false, true); diff --git a/common/src/main/java/com/fox2code/foxloader/registry/RegisteredWorld.java b/common/src/main/java/com/fox2code/foxloader/registry/RegisteredWorld.java index 5aa9aec..1495382 100644 --- a/common/src/main/java/com/fox2code/foxloader/registry/RegisteredWorld.java +++ b/common/src/main/java/com/fox2code/foxloader/registry/RegisteredWorld.java @@ -60,4 +60,11 @@ default List getRegisteredTileEntities() { default List getRegisteredNetworkPlayers() { throw new RuntimeException(); } + + /** + * @return the current dimension id + * + * Note: Multiples worlds with the same dimension ID can exist at a time + */ + default int getRegisteredDimensionID() { throw new RuntimeException(); } } diff --git a/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy b/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy index a8d7cba..00a6794 100644 --- a/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy +++ b/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy @@ -140,11 +140,20 @@ class GradlePlugin implements Plugin { if (config.foxLoaderLibVersionOverride != null) { foxLoaderVersion = config.foxLoaderLibVersionOverride } + final String foxLoaderGroupId = config.useLegacyFoxLoaderGroupId ? + "com.github.Fox2Code.FoxLoader" : "com.fox2code.FoxLoader" project.dependencies { runtimeOnly(BuildConfig.SPARK_DEPENDENCY) - implementation("com.github.Fox2Code.FoxLoader:common:${foxLoaderVersion}") - clientImplementation("com.github.Fox2Code.FoxLoader:client:${foxLoaderVersion}") - serverImplementation("com.github.Fox2Code.FoxLoader:server:${foxLoaderVersion}") + implementation("${foxLoaderGroupId}:common:${foxLoaderVersion}") + clientImplementation("${foxLoaderGroupId}:client:${foxLoaderVersion}") + serverImplementation("${foxLoaderGroupId}:server:${foxLoaderVersion}") + } + if (!config.useLegacyFoxLoaderGroupId) { + project.configurations.all { + exclude group: 'com.github.Fox2Code.FoxLoader', module: 'common' + exclude group: 'com.github.Fox2Code.FoxLoader', module: 'client' + exclude group: 'com.github.Fox2Code.FoxLoader', module: 'server' + } } if (config.useLWJGLX) { String lwjglNatives @@ -527,6 +536,7 @@ class GradlePlugin implements Plugin { public boolean forceReload = false public boolean unofficial = false public boolean useLWJGLX = false + public boolean useLegacyFoxLoaderGroupId public String LWJGLXVersion = "0.21" public String LWJGLXLWJGLVersion = "3.3.1" } diff --git a/gradle.properties b/gradle.properties index e6b5235..e027339 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,9 @@ org.gradle.parallel=true org.gradle.jvmargs=-Xmx1024m -XX:-UseGCOverheadLimit -Dfile.encoding=UTF-8 # FoxLoader properties -foxloader.version=1.2.25 +foxloader.version=1.2.26 foxloader.lastReIndevTransformerChanges=1.2.25 +# https://www.jitpack.io/#com.fox2code/FoxLoader # ReIndev properties reindev.clientUrl=https://cdn.fox2code.com/files/reindev_2.8.1_04.jar diff --git a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinEntityPlayerMP.java b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinEntityPlayerMP.java index 063d10c..92ce4de 100644 --- a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinEntityPlayerMP.java +++ b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinEntityPlayerMP.java @@ -112,4 +112,11 @@ public boolean isConnected() { public RegisteredItemStack getRegisteredHeldItem() { return ServerMod.toRegisteredItemStack(this.inventory.getCurrentItem()); } + + @Override + public void sendPlayerThroughPortalRegistered() { + this.mcServer.configManager.sendPlayerToOtherDimension( + ServerMod.toEntityPlayerMP(this)); + this.inPortal = false; + } } diff --git a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinNetServerHandler.java b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinNetServerHandler.java index bb38f00..7cc5632 100644 --- a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinNetServerHandler.java +++ b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinNetServerHandler.java @@ -5,13 +5,16 @@ import com.fox2code.foxloader.network.NetworkPlayer; import com.fox2code.foxloader.server.network.NetServerHandlerAccessor; import net.minecraft.src.game.entity.player.EntityPlayerMP; +import net.minecraft.src.server.ServerConfigurationManager; import net.minecraft.src.server.packets.NetServerHandler; +import net.minecraft.src.server.packets.Packet; import net.minecraft.src.server.packets.Packet250PluginMessage; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(NetServerHandler.class) @@ -19,6 +22,7 @@ public abstract class MixinNetServerHandler implements NetServerHandlerAccessor @Shadow private EntityPlayerMP playerEntity; @Unique private boolean hasFoxLoader; @Unique private boolean hasClientHello; + @Unique private String kickMessage; @Override public EntityPlayerMP getPlayerEntity() { @@ -59,4 +63,25 @@ public void onHandlePluginMessage(Packet250PluginMessage packet250, CallbackInfo modContainer.notifyReceiveClientPacket(networkPlayer, packet250.data); } } + + @Inject(method = "kickPlayer", at = @At("HEAD")) + public void onKickPlayer(String var1, CallbackInfo ci) { + this.kickMessage = var1; + } + + @Redirect(method = "kickPlayer", at = @At(value = "INVOKE", target = + "Lnet/minecraft/src/server/ServerConfigurationManager;sendPacketToAllPlayers(Lnet/minecraft/src/server/packets/Packet;)V")) + public void kickPlayerHook(ServerConfigurationManager instance, Packet packet) { + if (!ModLoader.Internal.notifyNetworkPlayerDisconnected((NetworkPlayer) this.playerEntity, this.kickMessage)) { + instance.sendPacketToAllPlayers(packet); + } + } + + @Redirect(method = "handleErrorMessage", at = @At(value = "INVOKE", target = + "Lnet/minecraft/src/server/ServerConfigurationManager;sendPacketToAllPlayers(Lnet/minecraft/src/server/packets/Packet;)V")) + public void handleErrorMessageHook(ServerConfigurationManager instance, Packet packet) { + if (!ModLoader.Internal.notifyNetworkPlayerDisconnected((NetworkPlayer) this.playerEntity, null)) { + instance.sendPacketToAllPlayers(packet); + } + } } diff --git a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinWorld.java b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinWorld.java index 23670bc..f1fb130 100644 --- a/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinWorld.java +++ b/server/src/main/java/com/fox2code/foxloader/server/mixins/MixinWorld.java @@ -28,6 +28,8 @@ public abstract class MixinWorld implements RegisteredWorld { @Shadow public abstract boolean setBlockAndMetadataWithNotify(int xCoord, int yCoord, int zCoord, int block, int metadata); @Shadow public abstract boolean entityJoinedWorld(Entity entity); + @Shadow public int dimension; + @Override public boolean hasRegisteredControl() { return true; @@ -81,6 +83,11 @@ public List getRegisteredNetworkPlayers() { return (List) (Object) this.playerEntities; } + @Override + public int getRegisteredDimensionID() { + return this.dimension; + } + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/src/game/level/IChunkProvider;unload100OldestChunks()Z")) public boolean hotfix_asyncKick(IChunkProvider instance) {