diff --git a/Fabric/build.gradle.kts b/Fabric/build.gradle.kts
index de54709..06253a0 100644
--- a/Fabric/build.gradle.kts
+++ b/Fabric/build.gradle.kts
@@ -6,10 +6,6 @@ plugins {
 	id("fabric-loom")
 }
 
-repositories {
-	maven("https://repo.spongepowered.org/maven")
-}
-
 dependencies {
 	minecraft("com.mojang:minecraft:$minecraftVersion")
 	modImplementation("net.fabricmc:fabric-loader:$fabricVersion")
@@ -18,8 +14,11 @@ dependencies {
 
 loom {
 	runs {
+		val runJvmArgs: Set<String> by project
+		
 		configureEach {
 			runDir("../run")
+			vmArgs(runJvmArgs)
 			ideConfigGenerated(true)
 		}
 		
@@ -32,7 +31,7 @@ loom {
 	}
 	
 	mixin {
-		add(sourceSets.main.get(), "$modId.refmap.json")
+		defaultRefmapName.set("$modId.refmap.json")
 	}
 }
 
diff --git a/NeoForge/build.gradle.kts b/NeoForge/build.gradle.kts
index b87633e..6aa5fc0 100644
--- a/NeoForge/build.gradle.kts
+++ b/NeoForge/build.gradle.kts
@@ -1,7 +1,4 @@
-val modId: String by project
-val minecraftVersion: String by project
 val neoForgeVersion: String by project
-val mixinVersion: String by project
 
 plugins {
 	id("net.neoforged.gradle.userdev")
@@ -13,16 +10,19 @@ dependencies {
 }
 
 runs {
+	val runJvmArgs: Set<String> by project
+	
 	configureEach {
-		modSource(project.sourceSets.main.get())
 		workingDirectory = file("../run")
+		modSource(project.sourceSets.main.get())
+		jvmArguments(runJvmArgs)
 	}
 	
 	create("client")
 }
 
 tasks.processResources {
-	filesMatching("META-INF/mods.toml") {
+	filesMatching("META-INF/neoforge.mods.toml") {
 		expand(inputs.properties)
 	}
 }
diff --git a/NeoForge/src/main/resources/META-INF/mods.toml b/NeoForge/src/main/resources/META-INF/neoforge.mods.toml
similarity index 94%
rename from NeoForge/src/main/resources/META-INF/mods.toml
rename to NeoForge/src/main/resources/META-INF/neoforge.mods.toml
index 94667fc..b29d45d 100644
--- a/NeoForge/src/main/resources/META-INF/mods.toml
+++ b/NeoForge/src/main/resources/META-INF/neoforge.mods.toml
@@ -18,14 +18,14 @@ config = "${id}.mixins.json"
 
 [[dependencies.${id}]]
 modId = "minecraft"
-mandatory = true
+type = "required"
 versionRange = "[${minimumMinecraftVersion},)"
 ordering = "NONE"
 side = "CLIENT"
 
 [[dependencies.${id}]]
 modId = "neoforge"
-mandatory = true
+type = "required"
 versionRange = "[${minimumNeoForgeVersion},)"
 ordering = "NONE"
 side = "CLIENT"
diff --git a/build.gradle.kts b/build.gradle.kts
index eab24bb..a572835 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -43,13 +43,12 @@ idea {
 }
 
 repositories {
-	maven("https://repo.spongepowered.org/maven")
 	mavenCentral()
 }
 
 dependencies {
-	implementation("org.spongepowered:mixin:$mixinVersion")
 	implementation("net.minecraft:client:$minecraftVersion")
+	compileOnly("net.fabricmc:sponge-mixin:$mixinVersion")
 	api("com.google.code.findbugs:jsr305:3.0.2")
 }
 
@@ -68,16 +67,24 @@ allprojects {
 	apply(plugin = "java-library")
 	
 	dependencies {
-		implementation("org.jetbrains:annotations:22.0.0")
+		implementation("org.jetbrains:annotations:24.1.0")
 	}
 	
 	extensions.getByType<JavaPluginExtension>().apply {
-		toolchain.languageVersion.set(JavaLanguageVersion.of(17))
+		toolchain.languageVersion.set(JavaLanguageVersion.of(21))
 	}
 	
 	tasks.withType<JavaCompile> {
 		options.encoding = "UTF-8"
-		options.release.set(17)
+		options.release.set(21)
+	}
+	
+	val runJvmArgs = mutableSetOf<String>().also {
+		extra["runJvmArgs"] = it
+	}
+	
+	if (project.javaToolchains.launcherFor(java.toolchain).map { it.metadata.vendor }.orNull == "JetBrains") {
+		runJvmArgs.add("-XX:+AllowEnhancedClassRedefinition")
 	}
 }
 
@@ -145,7 +152,7 @@ val copyJars = tasks.register<Copy>("copyJars") {
 		from(subproject.base.libsDirectory.file("${subproject.base.archivesName.get()}-$jarVersion.jar"))
 	}
 	
-	into(file("${project.buildDir}/dist"))
+	into(project.layout.buildDirectory.dir("dist"))
 }
 
 tasks.assemble {
diff --git a/gradle.properties b/gradle.properties
index d2ed352..b5b6141 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,15 +9,16 @@ modSourcesURL=https://github.com/chylex/Better-Controls
 modIssuesURL=https://github.com/chylex/Better-Controls/issues
 
 # Dependencies
-minecraftVersion=1.20.3
-neoForgeVersion=20.3.8-beta
-fabricVersion=0.14.21
+minecraftVersion=1.20.5
+neoForgeVersion=20.5.0-beta
+neoGradleVersion=7.0.120
+fabricVersion=0.15.11
 loomVersion=1.3
-mixinVersion=0.8.5
+mixinVersion=0.12.5+mixin.0.8.5
 
 # Constraints
-minimumMinecraftVersion=1.20.3
-minimumNeoForgeVersion=20.3.7
+minimumMinecraftVersion=1.20.5
+minimumNeoForgeVersion=20.5.0-beta
 minimumFabricVersion=0.12.3
 
 # Gradle
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..b82aa23 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.7-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index fcb6fca..1aa94a4 100755
--- a/gradlew
+++ b/gradlew
@@ -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/settings.gradle.kts b/settings.gradle.kts
index 7fba9eb..b6383b6 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,18 +5,19 @@ pluginManagement {
 		gradlePluginPortal()
 		maven(url = "https://maven.neoforged.net/releases") { name = "NeoForge" }
 		maven(url = "https://maven.fabricmc.net/") { name = "Fabric" }
-		maven(url = "https://repo.spongepowered.org/repository/maven-public/") { name = "Sponge Snapshots" }
 	}
 	
 	plugins {
-		if (settings.extra.has("neoForgeVersion")) {
-			id("net.neoforged.gradle.vanilla") version "7.0.61"
-			id("net.neoforged.gradle.userdev") version "7.0.61"
-			id("net.neoforged.gradle.mixin") version "7.0.61"
+		val neoGradleVersion = settings.extra.get("neoGradleVersion") as? String
+		if (neoGradleVersion != null) {
+			id("net.neoforged.gradle.vanilla") version neoGradleVersion
+			id("net.neoforged.gradle.userdev") version neoGradleVersion
+			id("net.neoforged.gradle.mixin") version neoGradleVersion
 		}
 		
-		if (settings.extra.has("loomVersion")) {
-			id("fabric-loom") version "${settings.extra["loomVersion"]}-SNAPSHOT"
+		val loomVersion = settings.extra.get("loomVersion") as? String
+		if (loomVersion != null) {
+			id("fabric-loom") version "$loomVersion-SNAPSHOT"
 		}
 	}
 }
diff --git a/src/main/resources/bettercontrols.mixins.json b/src/main/resources/bettercontrols.mixins.json
index 3ee539c..baaac28 100644
--- a/src/main/resources/bettercontrols.mixins.json
+++ b/src/main/resources/bettercontrols.mixins.json
@@ -3,7 +3,7 @@
   "minVersion": "0.8",
   "package": "chylex.bettercontrols.mixin",
   "refmap": "bettercontrols.refmap.json",
-  "compatibilityLevel": "JAVA_17",
+  "compatibilityLevel": "JAVA_21",
   "client": [
     "AccessCameraFields",
     "AccessClientPlayerFields",