diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..afd59d8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf diff --git a/LICENSE b/LICENSE index 44772e8..ae0f247 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023, flashwave +Copyright (c) 2023-2024, flashwave All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/build.gradle b/build.gradle index e6afdb5..7c0c455 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.3-SNAPSHOT' + id 'fabric-loom' version '1.7-SNAPSHOT' id 'maven-publish' } @@ -29,15 +29,15 @@ processResources { tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" - it.options.release = 17 + it.options.release = 21 it.options.compilerArgs += ['-Xlint:deprecation', '-Xlint:unchecked'] } java { withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } jar { diff --git a/gradle.properties b/gradle.properties index 65dcd5a..5bfc00d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,11 +4,11 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.20.1 -yarn_mappings=1.20.1+build.10 -loader_version=0.14.22 +minecraft_version=1.21 +yarn_mappings=1.21+build.8 +loader_version=0.15.11 # Mod Properties -mod_version=1.0.1 +mod_version=1.1.0 maven_group=net.flashii.mcexts archives_base_name=flashii-extensions diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca..b740cf1 100644 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/java/net/flashii/mcexts/RPC.java b/src/main/java/net/flashii/mcexts/RPC.java index 1ee64fa..1b7a789 100644 --- a/src/main/java/net/flashii/mcexts/RPC.java +++ b/src/main/java/net/flashii/mcexts/RPC.java @@ -8,8 +8,12 @@ import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; @@ -67,48 +71,52 @@ public class RPC { // sigStr and bodyStr because sigStr must also include the query params if those are present public static RPCPayload callRpc(String path, String sigStr, String bodyStr) - throws GeneralSecurityException, IOException { + throws GeneralSecurityException, IOException, InterruptedException { boolean hasBody = bodyStr != null; String time = getRequestTimestamp(); String hash = createRequestSignature(time, URLs.getRpcPath(path), sigStr); - HttpURLConnection conn = (HttpURLConnection)(new URL(URLs.getRpcUrl(path))).openConnection(); - conn.setRequestMethod(hasBody ? "POST" : "GET"); - conn.setRequestProperty("X-Mince-Time", time); - conn.setRequestProperty("X-Mince-Hash", hash); - - if(hasBody) { - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - - try(OutputStream stream = conn.getOutputStream()) { - byte[] input = bodyStr.getBytes(StandardCharsets.UTF_8); - stream.write(input, 0, input.length); - } + URI url; + try { + url = new URI(URLs.getRpcUrl(path)); + } catch(URISyntaxException ex) { + // fuck it + throw new IOException(ex.getMessage()); } - StringBuilder sb = new StringBuilder(); - try(BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { - String line; - while((line = br.readLine()) != null) - sb.append(line); - } + HttpClient client = HttpClient.newHttpClient(); + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(url) + .header("X-Mince-Time", time) + .header("X-Mince-Hash", hash); - return getGson().fromJson(sb.toString(), RPCPayload.class); + if(hasBody) + requestBuilder.header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString(bodyStr, StandardCharsets.UTF_8)); + else + requestBuilder.GET(); + + HttpRequest request = requestBuilder.build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if(response.statusCode() != 200) + throw new IOException("RPC request failed: " + response.statusCode()); + + return getGson().fromJson(response.body(), RPCPayload.class); } public static RPCPayload callRpc(String path, String paramStr) - throws GeneralSecurityException, IOException { + throws GeneralSecurityException, IOException, InterruptedException { return callRpc(path, paramStr, paramStr); } public static RPCPayload callRpc(String path, Map params) - throws GeneralSecurityException, IOException { + throws GeneralSecurityException, IOException, InterruptedException { return callRpc(path, createParamString(params)); } public static RPCPayload postAuth(UUID id, String name, InetAddress remoteAddr) - throws GeneralSecurityException, IOException { + throws GeneralSecurityException, IOException, InterruptedException { HashMap params = new HashMap<>(); params.put("id", id); params.put("name", name); @@ -118,7 +126,7 @@ public class RPC { } public static RPCPayload postAuth(UUID id, String name, SocketAddress sockAddr) - throws GeneralSecurityException, IOException { + throws GeneralSecurityException, IOException, InterruptedException { InetAddress remoteAddr = sockAddr instanceof InetSocketAddress ? ((InetSocketAddress)sockAddr).getAddress() : InetAddress.getLoopbackAddress(); diff --git a/src/main/java/net/flashii/mcexts/mixin/EnvironmentMixin.java b/src/main/java/net/flashii/mcexts/mixin/EnvironmentMixin.java deleted file mode 100644 index 2deaadf..0000000 --- a/src/main/java/net/flashii/mcexts/mixin/EnvironmentMixin.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.flashii.mcexts.mixin; - -import net.flashii.mcexts.URLs; -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.callback.CallbackInfoReturnable; - -@Mixin(targets = "com.mojang.authlib.Environment$1") -public abstract class EnvironmentMixin { - @Inject(method = "getSessionHost()Ljava/lang/String;", at = @At("HEAD"), cancellable = true, remap = false) - private void getSessionHost(CallbackInfoReturnable cir) { - cir.setReturnValue(URLs.getSessionHost()); - cir.cancel(); - } -} diff --git a/src/main/java/net/flashii/mcexts/mixin/PlayerListHudMixin.java b/src/main/java/net/flashii/mcexts/mixin/PlayerListHudMixin.java index ef407fc..31ce59c 100644 --- a/src/main/java/net/flashii/mcexts/mixin/PlayerListHudMixin.java +++ b/src/main/java/net/flashii/mcexts/mixin/PlayerListHudMixin.java @@ -8,7 +8,13 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(PlayerListHud.class) public abstract class PlayerListHudMixin { - @Redirect(method = "render(Lnet/minecraft/client/gui/DrawContext;ILnet/minecraft/scoreboard/Scoreboard;Lnet/minecraft/scoreboard/ScoreboardObjective;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;isInSingleplayer()Z")) + @Redirect( + method = "render(Lnet/minecraft/client/gui/DrawContext;ILnet/minecraft/scoreboard/Scoreboard;Lnet/minecraft/scoreboard/ScoreboardObjective;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/MinecraftClient;isInSingleplayer()Z" + ) + ) public boolean redirectIsInSinglePlayer(MinecraftClient client) { // always enable BL return true; diff --git a/src/main/java/net/flashii/mcexts/mixin/PlayerManagerMixin.java b/src/main/java/net/flashii/mcexts/mixin/PlayerManagerMixin.java index a734995..afb19e2 100644 --- a/src/main/java/net/flashii/mcexts/mixin/PlayerManagerMixin.java +++ b/src/main/java/net/flashii/mcexts/mixin/PlayerManagerMixin.java @@ -16,7 +16,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(PlayerManager.class) public abstract class PlayerManagerMixin { - @Inject(method = "checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;", at = @At("HEAD"), cancellable = true) + @Inject( + method = "checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;", + at = @At("HEAD"), + cancellable = true + ) private void checkCanJoin(SocketAddress sockAddr, GameProfile profile, CallbackInfoReturnable cir) { Text authText = null; @@ -60,6 +64,9 @@ public abstract class PlayerManagerMixin { } catch(GeneralSecurityException ex) { authText = Text.literal("Problem with request verification, yell at flashwave about this.").formatted(Formatting.RED); ex.printStackTrace(); + } catch(InterruptedException ex) { + authText = Text.literal("Problem with connecting to the Flashii authentication server, yell at flashwave about this.").formatted(Formatting.RED); + ex.printStackTrace(); } if(authText != null) { diff --git a/src/main/java/net/flashii/mcexts/mixin/PlayerSkinProviderMixin.java b/src/main/java/net/flashii/mcexts/mixin/PlayerSkinProviderMixin.java new file mode 100644 index 0000000..7602d40 --- /dev/null +++ b/src/main/java/net/flashii/mcexts/mixin/PlayerSkinProviderMixin.java @@ -0,0 +1,33 @@ +package net.flashii.mcexts.mixin; + +import com.mojang.authlib.GameProfile; +import java.util.concurrent.CompletableFuture; +import net.minecraft.client.texture.PlayerSkinProvider; +import net.minecraft.client.util.SkinTextures; +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.callback.CallbackInfoReturnable; + +@Mixin(PlayerSkinProvider.class) +public abstract class PlayerSkinProviderMixin { + @Inject( + method = "fetchSkinTextures(Lcom/mojang/authlib/GameProfile;)Ljava/util/concurrent/CompletableFuture;", + at = @At("RETURN"), + cancellable = true + ) + private void modifyFetchSkinTexturesReturn(GameProfile profile, CallbackInfoReturnable> cir) { + CompletableFuture future = cir.getReturnValue(); + cir.setReturnValue(future.thenApply(st -> { + return new SkinTextures( + st.texture(), + st.textureUrl(), + st.capeTexture(), + st.elytraTexture(), + st.model(), + true + ); + })); + cir.cancel(); + } +} diff --git a/src/main/java/net/flashii/mcexts/mixin/ServerMetadataMixin.java b/src/main/java/net/flashii/mcexts/mixin/ServerMetadataMixin.java index a6ce2bc..faeb02d 100644 --- a/src/main/java/net/flashii/mcexts/mixin/ServerMetadataMixin.java +++ b/src/main/java/net/flashii/mcexts/mixin/ServerMetadataMixin.java @@ -13,7 +13,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ServerMetadata.class) public abstract class ServerMetadataMixin { - @Inject(method = "description()Lnet/minecraft/text/Text;", at = @At("TAIL"), cancellable = true) + @Inject( + method = "description()Lnet/minecraft/text/Text;", + at = @At("TAIL"), + cancellable = true + ) public void description(CallbackInfoReturnable cir) { String linesRaw = Config.getValue("MOTDLines.txt"); if(linesRaw == null || linesRaw.isBlank()) diff --git a/src/main/java/net/flashii/mcexts/mixin/TextureUrlCheckerMixin.java b/src/main/java/net/flashii/mcexts/mixin/TextureUrlCheckerMixin.java index 3ce19fa..30b7723 100644 --- a/src/main/java/net/flashii/mcexts/mixin/TextureUrlCheckerMixin.java +++ b/src/main/java/net/flashii/mcexts/mixin/TextureUrlCheckerMixin.java @@ -9,7 +9,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(TextureUrlChecker.class) public abstract class TextureUrlCheckerMixin { - @Inject(method = "isAllowedTextureDomain(Ljava/lang/String;)Z", at = @At("HEAD"), cancellable = true, remap = false) + @Inject( + method = "isAllowedTextureDomain(Ljava/lang/String;)Z", + at = @At("HEAD"), + cancellable = true, + remap = false + ) private static void isAllowedTextureDomain(String url, CallbackInfoReturnable cir) { if(url == null || url.startsWith(URLs.getTexturesHostPrefix())) { cir.setReturnValue(true); diff --git a/src/main/java/net/flashii/mcexts/mixin/YggdrasilEnvironmentMixin.java b/src/main/java/net/flashii/mcexts/mixin/YggdrasilEnvironmentMixin.java new file mode 100644 index 0000000..6aaf5b6 --- /dev/null +++ b/src/main/java/net/flashii/mcexts/mixin/YggdrasilEnvironmentMixin.java @@ -0,0 +1,23 @@ +package net.flashii.mcexts.mixin; + +import com.mojang.authlib.Environment; +import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; +import net.flashii.mcexts.URLs; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(YggdrasilEnvironment.class) +public abstract class YggdrasilEnvironmentMixin { + @Redirect( + method = "", + at = @At( + value = "NEW", + target = "com/mojang/authlib/Environment", + ordinal = 0 + ) + ) + private Environment redirectEnvironmentCreation(String sessionHost, String servicesHost, String name) { + return new Environment(URLs.getSessionHost(), servicesHost, name); + } +} diff --git a/src/main/java/net/flashii/mcexts/mixin/YggdrasilMinecraftSessionServiceMixin.java b/src/main/java/net/flashii/mcexts/mixin/YggdrasilMinecraftSessionServiceMixin.java deleted file mode 100644 index 42eeca6..0000000 --- a/src/main/java/net/flashii/mcexts/mixin/YggdrasilMinecraftSessionServiceMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.flashii.mcexts.mixin; - -import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; - -@Mixin(YggdrasilMinecraftSessionService.class) -public abstract class YggdrasilMinecraftSessionServiceMixin { - @ModifyVariable(method = "getTextures(Lcom/mojang/authlib/GameProfile;Z)Ljava/util/Map;", at = @At("HEAD"), argsOnly = true, remap = false) - private boolean interceptGetTexturesRequireSecure(boolean requireSecure) { - return false; - } - - @ModifyVariable(method = "fillProfileProperties(Lcom/mojang/authlib/GameProfile;Z)Lcom/mojang/authlib/GameProfile;", at = @At("HEAD"), argsOnly = true, remap = false) - private boolean interceptFillProfilePropertiesRequireSecure(boolean requireSecure) { - return true; - } - - @ModifyVariable(method = "fillGameProfile(Lcom/mojang/authlib/GameProfile;Z)Lcom/mojang/authlib/GameProfile;", at = @At("HEAD"), argsOnly = true, remap = false) - private boolean interceptFillGameProfileRequireSecure(boolean requireSecure) { - return false; - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7c3d804..38bc140 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -19,7 +19,7 @@ ], "depends": { "fabricloader": ">=0.14.22", - "minecraft": "~1.20.1", - "java": ">=17" + "minecraft": ">=1.21", + "java": ">=21" } } diff --git a/src/main/resources/flashii-extensions.mixins.json b/src/main/resources/flashii-extensions.mixins.json index 46b07ae..c73f99f 100644 --- a/src/main/resources/flashii-extensions.mixins.json +++ b/src/main/resources/flashii-extensions.mixins.json @@ -1,12 +1,12 @@ { "required": true, "package": "net.flashii.mcexts.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "mixins": [ - "EnvironmentMixin", + "PlayerSkinProviderMixin", "PlayerListHudMixin", "TextureUrlCheckerMixin", - "YggdrasilMinecraftSessionServiceMixin" + "YggdrasilEnvironmentMixin" ], "server": [ "PlayerManagerMixin",