feat(ScriptAPI): Implement GraalJS JIT with call target remapping (#4368)

Improves ScriptAPI by enabling GraalJS JIT while addressing Minecraft's obfuscation. Previously, non-JIT mode allowed remapping; however, this new approach applies a call target remapping strategy, overcoming bugs with overloaded names.

Also, GraalVM is now the default JDK on LiquidLauncher.

---------

Co-authored-by: Senk Ju <18741573+SenkJu@users.noreply.github.com>
This commit is contained in:
1zuna
2024-11-06 03:33:08 +00:00
committed by GitHub
parent d4c551ca2d
commit 591c5793a9
17 changed files with 473 additions and 416 deletions

View File

@@ -19,8 +19,8 @@ jobs:
- name: Setting up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
distribution: 'graalvm'
java-version: '21'
- name: Grant permissions to src-theme
run: sudo chmod -R 777 src-theme
@@ -50,7 +50,7 @@ jobs:
cd zip
zip -r liquidbounce.zip *
md5sum liquidbounce.zip
curl --connect-timeout 30 -m 300 -X POST -F "artifact=@liquidbounce.zip" -H "Authorization: ${{ secrets.NIGHTLY_PASS }}" -F "gh_id=${{ github.event.head_commit.id }}" -F "gh_ref=${{ github.ref }}" -F "gh_message=${{ github.event.head_commit.message }}" -F "gh_timestamp=${{ github.event.head_commit.timestamp }}" -F "lb_version=$LB_VERSION" -F "mc_version=$MINECRAFT_VERSION" -F "subsystem=fabric" -F "jre_version=21" -F "fabric_loader_version=$LOADER_VERSION" -F "fabric_api_version=$FABRICAPI_VERSION" -F "kotlin_version=$KOTLIN_VERSION" -F "fabric_kotlin_version=$FABRIC_KOTLIN_VERSION" https://api.liquidbounce.net/api/v1/version/new
curl --connect-timeout 30 -m 300 -X POST -F "artifact=@liquidbounce.zip" -H "Authorization: ${{ secrets.NIGHTLY_PASS }}" -F "gh_id=${{ github.event.head_commit.id }}" -F "gh_ref=${{ github.ref }}" -F "gh_message=${{ github.event.head_commit.message }}" -F "gh_timestamp=${{ github.event.head_commit.timestamp }}" -F "lb_version=$LB_VERSION" -F "mc_version=$MINECRAFT_VERSION" -F "subsystem=fabric" -F "jre_version=21" -F "jre_distribution=graalvm" -F "fabric_loader_version=$LOADER_VERSION" -F "fabric_api_version=$FABRICAPI_VERSION" -F "kotlin_version=$KOTLIN_VERSION" -F "fabric_kotlin_version=$FABRIC_KOTLIN_VERSION" https://api.liquidbounce.net/api/v1/version/new
verify-pr:
runs-on: ubuntu-latest
@@ -62,10 +62,10 @@ jobs:
submodules: recursive
- name: Setting up JDK 21
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
distribution: 'graalvm'
java-version: '21'
- name: Grant permissions to src-theme
run: sudo chmod -R 777 src-theme

View File

@@ -20,41 +20,42 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id 'fabric-loom'
id 'org.jetbrains.kotlin.jvm'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.gorylenko.gradle-git-properties' version '2.4.2'
id "fabric-loom"
id "org.jetbrains.kotlin.jvm"
id "com.gorylenko.gradle-git-properties" version "2.4.2"
id "io.gitlab.arturbosch.detekt" version "1.23.6"
id "com.github.node-gradle.node" version "7.1.0"
id 'org.jetbrains.dokka' version '1.9.10'
id "org.jetbrains.dokka" version "1.9.10"
}
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
loom {
accessWidenerPath = file('src/main/resources/liquidbounce.accesswidener')
configurations {
includeDependency
includeModDependency
include.extendsFrom includeModDependency
modImplementation.extendsFrom includeModDependency
modCompileOnlyApi.extendsFrom includeModDependency
}
repositories {
mavenCentral()
mavenLocal()
maven { url = 'https://maven.fabricmc.net/' }
maven { url = "https://maven.fabricmc.net/" }
maven {
name = 'Jitpack'
url = 'https://jitpack.io'
name = "Jitpack"
url = "https://jitpack.io"
}
maven {
name = 'TerraformersMC'
url = 'https://maven.terraformersmc.com/'
name = "TerraformersMC"
url = "https://maven.terraformersmc.com/"
}
maven {
name = 'ViaVersion'
url = 'https://repo.viaversion.com/'
name = "ViaVersion"
url = "https://repo.viaversion.com/"
}
maven {
name = "modrinth"
@@ -70,83 +71,80 @@ repositories {
}
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
loom {
accessWidenerPath = file("src/main/resources/liquidbounce.accesswidener")
}
dependencies {
// Minecraft
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// Libraries (required mods)
// Fabric
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}"
implementation "net.fabricmc:tiny-mappings-parser:0.3.0+build.17"
// Recommended mods (on IDE)
modRuntimeOnly "com.terraformersmc:modmenu:${project.mod_menu_version}"
modImplementation "maven.modrinth:sodium:${project.sodium_version}"
modImplementation "de.florianmichael:ViaFabricPlus:${project.viafabricplus_version}"
modCompileOnly "de.florianmichael:ViaFabricPlus:${project.viafabricplus_version}"
// Tests
// modImplementation 'com.github.superblaubeere27:tenacc:e3a7ada99a'
// fix nullable imports
implementation 'com.google.code.findbugs:jsr305:3.0.2'
// Client libraries
implementation("com.github.CCBlueX:mc-authlib:${project.mc_authlib_version}") {
exclude group: 'com.google.code.gson', module: 'gson'
exclude group: 'org.apache.logging.log4j', module: 'log4j-core'
exclude group: 'org.apache.logging.log4j', module: 'log4j-api'
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
exclude group: 'org.slf4j', module: 'slf4j-api'
exclude group: 'com.mojang', module: 'authlib'
// Minecraft Authlib
includeDependency ("com.github.CCBlueX:mc-authlib:${project.mc_authlib_version}") {
exclude group: "com.google.code.gson", module: "gson"
exclude group: "org.apache.logging.log4j", module: "log4j-core"
exclude group: "org.apache.logging.log4j", module: "log4j-api"
exclude group: "org.apache.logging.log4j", module: "log4j-slf4j-impl"
exclude group: "org.slf4j", module: "slf4j-api"
exclude group: "com.mojang", module: "authlib"
}
implementation "com.github.CCBlueX:mcef:916f8c5f73"
implementation 'com.github.CCBlueX:netty-httpserver:2.1.0'
implementation "com.github.CCBlueX:DiscordIPC:-SNAPSHOT"
implementation 'org.graalvm.sdk:graal-sdk:23.0.5'
implementation 'org.graalvm.truffle:truffle-api:23.0.5'
implementation 'org.graalvm.js:js:23.0.5'
// JCEF Support
includeModDependency "com.github.CCBlueX:mcef:1.1.5-1.21.1"
includeDependency "org.apache.commons:commons-exec:1.3"
includeDependency ("com.github.CCBlueX:netty-httpserver:2.1.0") {
exclude group: "io.netty", module: "netty-all"
}
runtimeOnly 'org.graalvm.tools:profiler:23.0.5'
runtimeOnly 'org.graalvm.tools:chromeinspector:23.0.5'
// Discord RPC Support
includeDependency "com.github.CCBlueX:DiscordIPC:4.0.0"
// https://mvnrepository.com/artifact/io.netty/netty-codec
implementation 'io.netty:netty-codec:4.1.82.Final'
// https://mvnrepository.com/artifact/io.netty/netty-codec-http
implementation 'io.netty:netty-codec-http:4.1.82.Final'
// https://mvnrepository.com/artifact/io.netty/netty-handler-proxy
implementation 'io.netty:netty-handler-proxy:4.1.82.Final'
// https://mvnrepository.com/artifact/io.netty/netty-codec-socks
implementation 'io.netty:netty-codec-socks:4.1.82.Final'
// ScriptAPI
includeDependency "net.fabricmc:tiny-mappings-parser:0.3.0+build.17"
includeDependency "org.graalvm.polyglot:polyglot:24.0.2"
includeDependency "org.graalvm.polyglot:js-community:24.0.2"
implementation 'com.vdurmont:semver4j:3.1.0'
implementation 'org.apache.tika:tika-core:3.0.0'
// SOCKS5 Proxy Support (to stay compatible with ViaFabricPlus)
includeDependency "io.netty:netty-handler-proxy:4.1.114.Final"
implementation 'org.apache.commons:commons-exec:1.3'
// Update Checker
includeDependency "com.vdurmont:semver4j:3.1.0"
// Test libraries
testImplementation "org.junit.jupiter:junit-jupiter:5.11.3"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
afterEvaluate {
configurations.includeDependency.incoming.resolutionResult.allDependencies.each {
dependencies.include(dependencies.implementation(dependencies.compileOnlyApi(it.requested.toString()) {
transitive = false
}))
}
}
}
processResources {
inputs.property 'version', project.version
inputs.property "version", project.version
inputs.property 'minecraft_version', minecraft_version
inputs.property 'fabric_version', fabric_version
inputs.property 'loader_version', loader_version
inputs.property 'min_loader_version', min_loader_version
inputs.property 'fabric_kotlin_version', fabric_kotlin_version
inputs.property 'viafabricplus_version', viafabricplus_version
inputs.property "minecraft_version", minecraft_version
inputs.property "fabric_version", fabric_version
inputs.property "loader_version", loader_version
inputs.property "min_loader_version", min_loader_version
inputs.property "fabric_kotlin_version", fabric_kotlin_version
inputs.property "viafabricplus_version", viafabricplus_version
filesMatching('fabric.mod.json') {
filesMatching("fabric.mod.json") {
expand([
version : project.version,
minecraft_version : minecraft_version,
@@ -161,60 +159,60 @@ processResources {
// The following code will include the theme into the build
tasks.register('npmInstallTheme', NpmTask) {
workingDir = file('src-theme')
args = ['i']
tasks.register("npmInstallTheme", NpmTask) {
workingDir = file("src-theme")
args = ["i"]
doLast {
println 'Successfully installed dependencies for theme'
println "Successfully installed dependencies for theme"
}
inputs.files('src-theme/package.json', 'src-theme/package-lock.json')
inputs.files("src-theme/package.json", "src-theme/package-lock.json")
outputs.dir("src-theme/node_modules")
}
tasks.register('buildTheme', NpmTask) {
dependsOn 'npmInstallTheme'
workingDir = file('src-theme')
args = ['run', 'build']
tasks.register("buildTheme", NpmTask) {
dependsOn "npmInstallTheme"
workingDir = file("src-theme")
args = ["run", "build"]
doLast {
println 'Successfully build theme'
println "Successfully build theme"
}
inputs.files(
'src-theme/package.json',
'src-theme/package-lock.json',
'src-theme/bundle.cjs',
'src-theme/rollup.config.js'
"src-theme/package.json",
"src-theme/package-lock.json",
"src-theme/bundle.cjs",
"src-theme/rollup.config.js"
)
inputs.dir("src-theme/src")
outputs.dir('src-theme/dist')
outputs.dir("src-theme/dist")
}
tasks.register('bundleTheme', NodeTask) {
dependsOn 'buildTheme'
workingDir = file('src-theme')
script = file('src-theme/bundle.cjs')
tasks.register("bundleTheme", NodeTask) {
dependsOn "buildTheme"
workingDir = file("src-theme")
script = file("src-theme/bundle.cjs")
doLast {
println 'Successfully attached theme to build'
println "Successfully attached theme to build"
}
// Incremental stuff
inputs.files(
'src-theme/package.json',
'src-theme/package-lock.json',
'src-theme/bundle.cjs',
'src-theme/rollup.config.js'
"src-theme/package.json",
"src-theme/package-lock.json",
"src-theme/bundle.cjs",
"src-theme/rollup.config.js"
)
inputs.dir("src-theme/src")
inputs.dir("src-theme/public")
inputs.dir("src-theme/dist")
outputs.files('src-theme/resources/assets/liquidbounce/default_theme.zip')
outputs.files("src-theme/resources/assets/liquidbounce/default_theme.zip")
}
sourceSets {
main {
resources {
srcDirs 'src-theme/resources'
srcDirs "src-theme/resources"
}
}
}
@@ -229,7 +227,7 @@ tasks.withType(JavaCompile).configureEach {
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = 'UTF-8'
it.options.encoding = "UTF-8"
// Minecraft 1.20.5 upwards uses Java 17.
it.options.release = 21
@@ -237,6 +235,7 @@ tasks.withType(JavaCompile).configureEach {
tasks.withType(Test).configureEach {
useJUnitPlatform()
dependsOn(tasks.named("genSources"))
}
detekt {
@@ -264,6 +263,9 @@ java {
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
compileKotlin {
@@ -273,49 +275,22 @@ compileKotlin {
}
}
shadowJar {
archiveClassifier.set('shadow')
jar {
// Rename the project"s license file to LICENSE_<project_name> to avoid conflicts
from("LICENSE") {
rename {
"${it}_${project.archives_base_name}"
}
}
// Natives are going to be downloaded from our cloud
exclude 'native-binaries/*'
// META-INF/versions/20
exclude "META-INF/versions/20/**"
dependencies {
include(dependency('com.github.CCBlueX:mcef'))
include(dependency('com.github.CCBlueX:netty-httpserver'))
include(dependency('com.github.CCBlueX:mc-authlib'))
include(dependency('com.github.CCBlueX:DiscordIPC'))
include(dependency('com.thealtening.api:api'))
include(dependency('net.fabricmc:tiny-mappings-parser'))
include(dependency('org.graalvm.sdk:graal-sdk'))
include(dependency('org.graalvm.truffle:truffle-api'))
include(dependency('org.graalvm.js:js'))
include(dependency('io.netty:netty-codec'))
include(dependency('io.netty:netty-codec-http'))
include(dependency('io.netty:netty-handler-proxy'))
include(dependency('io.netty:netty-codec-socks'))
include(dependency('org.apache.commons:commons-exec'))
include(dependency('org.apache.tika:tika-core'))
include(dependency('com.vdurmont:semver4j'))
include(dependency('com.kohlschutter.junixsocket:junixsocket-common'))
include(dependency('com.kohlschutter.junixsocket:junixsocket-native-common'))
include(dependency('net.lenni0451:Reflect'))
from(configurations.mappings.collect { zipTree(it) }) {
include "mappings/mappings.tiny"
}
}
jar {
from 'LICENSE'
}
remapJar {
inputFile = shadowJar.archiveFile
}
tasks.register('copyZipInclude', Copy) {
from 'zip_include/'
into 'build/libs/zip'
tasks.register("copyZipInclude", Copy) {
from "zip_include/"
into "build/libs/zip"
}
sourcesJar.dependsOn bundleTheme

View File

@@ -22,13 +22,13 @@ org.gradle.jvmargs=-Xms1024m -Xmx4096m
# Check these on https://fabricmc.net/versions.html
minecraft_version=1.21.1
yarn_mappings=1.21.1+build.3
loader_version=0.16.3
loader_version=0.16.9
min_loader_version=0.15.10
# Fabric API
fabric_version=0.102.1+1.21.1
fabric_version=0.107.0+1.21.1
# Loom
loom_version=1.7-SNAPSHOT
loom_version=1.8-SNAPSHOT
# Mod Properties
mod_version=0.17.1
maven_group=net.ccbluex

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -19,7 +19,6 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
@@ -33,5 +32,3 @@ pluginManagement {
}
}
//include('mcef')

View File

@@ -1,61 +0,0 @@
/*
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
*
* Copyright (c) 2015 - 2024 CCBlueX
*
* LiquidBounce is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiquidBounce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.injection.mixins.graaljs;
import net.ccbluex.liquidbounce.utils.mappings.Remapper;
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.ModifyVariable;
/**
* Remaps class method and field names to their obfuscated counterparts.
*
* Initial code by lit
*/
@Mixin(targets = "com/oracle/truffle/host/HostClassDesc")
public abstract class MixinHostClassDesc {
@Shadow
public abstract Class<?> getType();
@ModifyVariable(method = "lookupField(Ljava/lang/String;)Lcom/oracle/truffle/host/HostFieldDesc;",
at = @At("HEAD"), argsOnly = true, index = 1, remap = false)
private String remapFieldName(String name) {
return Remapper.INSTANCE.remapField(getType(), name, true);
}
@ModifyVariable(method = "lookupStaticField", at = @At("HEAD"), argsOnly = true, index = 1, remap = false)
private String remapStaticFieldName(String name) {
return Remapper.INSTANCE.remapField(getType(), name, true);
}
@ModifyVariable(method = "lookupMethod(Ljava/lang/String;)Lcom/oracle/truffle/host/HostMethodDesc;",
at = @At("HEAD"), argsOnly = true, index = 1, remap = false)
private String remapMethodName(String name) {
return Remapper.INSTANCE.remapMethod(getType(), name, true);
}
@ModifyVariable(method = "lookupStaticMethod", at = @At("HEAD"), argsOnly = true, index = 1, remap = false)
private String remapStaticMethodName(String name) {
return Remapper.INSTANCE.remapMethod(getType(), name, true);
}
}

View File

@@ -1,17 +0,0 @@
package net.ccbluex.liquidbounce.injection.mixins.graaljs;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(targets = "com/oracle/truffle/api/TruffleLanguage")
public class MixinTruffleLanguage {
/**
* @author Senk Ju
* @reason Prevent GraalVM from blocking multi threaded access to resources
*/
@Overwrite(remap = false)
protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
return true;
}
}

View File

@@ -0,0 +1,140 @@
/*
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
*
* Copyright (c) 2015 - 2024 CCBlueX
*
* LiquidBounce is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiquidBounce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.injection.mixins.truffle;
import net.ccbluex.liquidbounce.interfaces.MemberRetriever;
import net.ccbluex.liquidbounce.utils.client.ClientUtilsKt;
import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Pseudo
@Mixin(targets = "com/oracle/truffle/host/HostClassDesc$Members")
public abstract class MixinHostClassDesc {
@Shadow(remap = false)
@Final
Map<String, Object> methods;
@Shadow(remap = false)
@Final
Map<String, Object> fields;
@Shadow(remap = false)
@Final
Map<String, Object> staticFields;
@Shadow(remap = false)
@Final
Map<String, Object> staticMethods;
@Inject(method = "<init>", at = @At("RETURN"), remap = false)
private void remapClassDesc(CallbackInfo ci) {
remapEntries(methods, this::getMethod);
remapEntries(fields, this::getField);
remapEntries(staticFields, this::getField);
remapEntries(staticMethods, this::getMethod);
}
@Unique
private void remapEntries(Map<String, Object> map, MemberRetriever retriever) {
var entries = new HashMap<>(map).entrySet();
for (var entry : entries) {
String key = entry.getKey();
Object value = entry.getValue();
String remapped;
try {
Member member = retriever.getMember(value);
remapped = remapDescriptor(member);
} catch (ReflectiveOperationException e) {
ClientUtilsKt.getLogger().error("Failed to remap: {}", key, e);
continue;
}
if (remapped != null) {
map.remove(key);
map.put(remapped, value);
}
}
}
@Unique
private Member getMethod(Object o) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
try {
// If this works, it is likely a SingleMethod instance
Method descMethod = o.getClass().getDeclaredMethod("getReflectionMethod");
descMethod.setAccessible(true);
return (Member) descMethod.invoke(o);
} catch (NoSuchMethodException ignored) {
try {
var getOverloads = o.getClass().getDeclaredMethod("getOverloads");
var overloads = (Object[]) getOverloads.invoke(o);
return getMethod(overloads[0]);
} catch (NoSuchMethodException ignored2) {
ClientUtilsKt.getLogger().error("Unsupported method type: {}", o.getClass().getName());
}
return null;
}
}
@Unique
private Member getField(Object o) throws IllegalAccessException, NoSuchFieldException {
var descField = o.getClass().getDeclaredField("field");
descField.setAccessible(true);
return (Member) descField.get(o);
}
@Unique
private static String remapDescriptor(Member member) {
var name = member.getName();
String remapped;
if (member instanceof java.lang.reflect.Method) {
remapped = EnvironmentRemapper.INSTANCE.remapMethod(member.getDeclaringClass(), name);
} else if (member instanceof java.lang.reflect.Field) {
remapped = EnvironmentRemapper.INSTANCE.remapField(member.getDeclaringClass(), name);
} else {
ClientUtilsKt.getLogger().error("Unknown member type: {}", member.getClass().getName());
return null;
}
// If the name is the same, return the original field
if (name.equals(remapped)) {
return null;
}
// ClientUtilsKt.getLogger().debug("Remapped descriptor: {} in {} to {}", name, member.getDeclaringClass().getName(), remapped);
return remapped;
}
}

View File

@@ -17,24 +17,21 @@
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.injection.mixins.graaljs;
package net.ccbluex.liquidbounce.injection.mixins.truffle;
import net.ccbluex.liquidbounce.utils.mappings.Remapper;
import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
/**
* Remaps class names to their obfuscated counterparts.
*
* Initial code by lit
*/
@Mixin(targets = "com/oracle/truffle/host/HostClassLoader")
@Pseudo
@Mixin(targets = "com/oracle/truffle/host/HostClassLoader", remap = false)
public class MixinHostClassLoader {
@ModifyVariable(method = "findClass", at = @At("HEAD"), argsOnly = true, remap = false)
private String remapClassName(String value) {
return Remapper.INSTANCE.remapClassName(value);
return EnvironmentRemapper.INSTANCE.remapClassName(value);
}
}

View File

@@ -19,19 +19,21 @@
*
*/
package net.ccbluex.liquidbounce.injection.mixins.graaljs;
package net.ccbluex.liquidbounce.injection.mixins.truffle;
import net.ccbluex.liquidbounce.utils.mappings.Remapper;
import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(targets = "com/oracle/truffle/host/HostContext")
@Pseudo
@Mixin(targets = "com/oracle/truffle/host/HostContext", remap = false)
public class MixinHostContext {
@ModifyVariable(method = "findClassImpl", at = @At("HEAD"), argsOnly = true, remap = false)
private String remapClassName(String value) {
return Remapper.INSTANCE.remapClassName(value);
return EnvironmentRemapper.INSTANCE.remapClassName(value);
}
}

View File

@@ -0,0 +1,38 @@
/*
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
*
* Copyright (c) 2015 - 2024 CCBlueX
*
* LiquidBounce is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiquidBounce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.injection.mixins.truffle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Pseudo;
@Pseudo
@Mixin(targets = "com/oracle/truffle/api/TruffleLanguage", remap = false)
public class MixinTruffleLanguage {
/**
* @author Senk Ju
* @reason Prevent GraalVM from blocking multithreaded access to resources
*/
@Overwrite(remap = false)
protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
return true;
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
*
* Copyright (c) 2015 - 2024 CCBlueX
*
* LiquidBounce is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiquidBounce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.interfaces;
import java.lang.reflect.Member;
@FunctionalInterface
public interface MemberRetriever {
Member getMember(Object o) throws ReflectiveOperationException;
}

View File

@@ -62,7 +62,7 @@ import net.ccbluex.liquidbounce.utils.combat.CombatManager
import net.ccbluex.liquidbounce.utils.combat.combatTargetsConfigurable
import net.ccbluex.liquidbounce.utils.input.InputTracker
import net.ccbluex.liquidbounce.utils.inventory.InventoryManager
import net.ccbluex.liquidbounce.utils.mappings.Remapper
import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper
import net.ccbluex.liquidbounce.utils.render.WorldToScreen
import net.minecraft.resource.ReloadableResourceManagerImpl
import net.minecraft.resource.ResourceManager
@@ -122,7 +122,7 @@ object LiquidBounce : Listenable {
logger.debug("Loading from cloud: '$CLIENT_CLOUD'")
// Load mappings
Remapper.load()
EnvironmentRemapper
// Load translations
LanguageManager.loadLanguages()

View File

@@ -18,13 +18,13 @@
*/
package net.ccbluex.liquidbounce.script.bindings.api
import net.ccbluex.liquidbounce.utils.mappings.Remapper
import net.ccbluex.liquidbounce.utils.mappings.EnvironmentRemapper
object JsReflectionUtil {
@JvmName("classByName")
fun classByName(name: String): Class<*> = Class.forName(
Remapper.remapClassName(name).replace('/', '.')
EnvironmentRemapper.remapClassName(name).replace('/', '.')
)
@JvmName("classByObject")
@@ -38,7 +38,7 @@ object JsReflectionUtil {
@JvmName("newInstanceByName")
fun newInstanceByName(name: String, vararg args: Any?): Any? =
Class.forName(Remapper.remapClassName(name).replace('/', '.'))
Class.forName(EnvironmentRemapper.remapClassName(name).replace('/', '.'))
.getDeclaredConstructor(*args.map { it!!::class.java }.toTypedArray()).apply {
isAccessible = true
}.newInstance(*args)
@@ -50,35 +50,37 @@ object JsReflectionUtil {
}.newInstance(*args)
@JvmName("getField")
fun getField(obj: Any, name: String): Any? = obj::class.java.getDeclaredField(
Remapper.remapField(obj::class.java, name, true)
).apply {
isAccessible = true
}.get(obj)
fun getField(obj: Any, name: String): Any? = obj::class.java.fields
.find { field ->
field.name == EnvironmentRemapper.remapField(obj::class.java, name)
}?.apply {
isAccessible = true
}?.get(obj)
@JvmName("getStaticField")
fun getStaticField(clazz: Class<*>, name: String): Any? = clazz.getDeclaredField(
Remapper.remapField(clazz, name, true)
).apply {
isAccessible = true
}.get(null)
@JvmName("getDeclaredField")
fun getDeclaredField(clazz: Class<*>, name: String): Any? = clazz.declaredFields
.find { field ->
field.name == EnvironmentRemapper.remapField(clazz, name)
}?.apply {
isAccessible = true
}?.get(null)
@JvmName("invokeMethod")
fun invokeMethod(obj: Any, name: String, vararg args: Any?): Any? =
obj::class.java.getDeclaredMethod(
Remapper.remapField(obj::class.java, name, true),
*args.map { it!!::class.java }.toTypedArray()
).apply {
obj::class.java.methods.find { method ->
method.name == EnvironmentRemapper.remapMethod(obj::class.java, name) &&
method.parameterTypes.contentEquals(args.map { it!!::class.java }.toTypedArray())
}?.apply {
isAccessible = true
}.invoke(obj, *args)
}?.invoke(obj, *args)
@JvmName("invokeStaticMethod")
fun invokeStaticMethod(clazz: Class<*>, name: String, vararg args: Any?): Any? =
clazz.getDeclaredMethod(
Remapper.remapField(clazz, name, true),
*args.map { it!!::class.java }.toTypedArray()
).apply {
@JvmName("invokeDeclaredMethod")
fun invokeDeclaredMethod(clazz: Class<*>, name: String, vararg args: Any?): Any? =
clazz.declaredMethods.find { method ->
method.name == EnvironmentRemapper.remapMethod(clazz, name) &&
method.parameterTypes.contentEquals(args.map { it!!::class.java }.toTypedArray())
}?.apply {
isAccessible = true
}.invoke(null, *args)
}?.invoke(null, *args)
}

View File

@@ -0,0 +1,102 @@
package net.ccbluex.liquidbounce.utils.mappings
import net.ccbluex.liquidbounce.utils.client.logger
import net.ccbluex.liquidbounce.utils.io.resource
import net.fabricmc.mappings.model.V2MappingsProvider
object EnvironmentRemapper {
private var mappings = runCatching {
V2MappingsProvider.readTinyMappings(resource("/mappings/mappings.tiny").bufferedReader())
}.onFailure {
logger.error("Unable to load mappings. Ignore this if you are using a development environment.", it)
}.getOrNull()
private var environment = runCatching {
probeEnvironment()
}.onFailure {
logger.error("Unable to probe environment. Please make sure you are using a valid environment.", it)
}.getOrNull()
private fun probeEnvironment(): String? {
val mappings = mappings ?: return null
val minecraftClassEntry = mappings.classEntries?.find { entry ->
entry?.get("named") == "net/minecraft/client/MinecraftClient"
}
if (minecraftClassEntry == null) {
logger.error("Unable to probe environment. Please make sure you are using a valid environment.")
return null
}
logger.info("Probing environment...")
return when {
isClassPresent(minecraftClassEntry.get("intermediary")?.toDotNotation()) -> {
logger.info("Intermediary environment detected.")
"intermediary"
}
else -> {
logger.error("No matching environment detected. Please make sure you are using a valid environment.")
null
}
}
}
private fun isClassPresent(className: String?): Boolean {
return try {
Class.forName(className)
true
} catch (_: ClassNotFoundException) {
false
}
}
fun remapClassName(clazz: String): String {
environment ?: return clazz
val className = clazz.toSlashNotation()
return mappings?.classEntries?.find {
it?.get("named") == className
}?.get(environment)?.toDotNotation() ?: clazz
}
fun remapField(clazz: Class<*>, name: String): String {
environment ?: return name
val clazzNames = getClassHierarchyNames(clazz)
return mappings?.fieldEntries?.find { entry ->
val intern = entry.get(environment)
clazzNames.contains(intern.owner) && intern.name == name
}?.get("named")?.name ?: name
}
fun remapMethod(clazz: Class<*>, name: String): String {
environment ?: return name
val clazzNames = getClassHierarchyNames(clazz)
return mappings?.methodEntries?.find { entry ->
val intern = entry.get(environment)
clazzNames.contains(intern.owner) && intern.name == name
}?.get("named")?.name ?: name
}
private fun getClassHierarchyNames(clazz: Class<*>): Set<String> {
val clazzNames = mutableSetOf(clazz.name.toSlashNotation())
var current = clazz
while (current.name != "java.lang.Object") {
current = current.superclass ?: break
clazzNames.add(current.name.toSlashNotation())
}
return clazzNames
}
private fun String.toDotNotation(): String = replace('/', '.')
private fun String.toSlashNotation(): String = replace('.', '/')
}

View File

@@ -1,145 +0,0 @@
/*
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
*
* Copyright (c) 2015 - 2024 CCBlueX
*
* LiquidBounce is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiquidBounce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
*/
package net.ccbluex.liquidbounce.utils.mappings
import net.ccbluex.liquidbounce.utils.client.logger
import net.ccbluex.liquidbounce.utils.io.resource
import net.fabricmc.mappings.Mappings
import net.fabricmc.mappings.model.V2MappingsProvider
/**
* Tiny mappings
*
* These are fabric mappings which are being exported when the jar is being built.
* It allows you to remap obfuscated environments into readable names.
*
* This is going to be used for ultralight-databind and our JS script engine.
*/
object Remapper {
var mappings: Mappings? = null
var environment: String? = null
fun load() {
runCatching {
mappings = V2MappingsProvider.readTinyMappings(resource("/mappings/mappings.tiny").bufferedReader())
}.onFailure {
logger.error("Unable to load mappings. Ignore this if you are using a development environment.", it)
}
// Probe environment
runCatching {
probeEnvironment()
}.onFailure {
logger.error("Unable to probe environment. Please make sure you are using a valid environment.", it)
}
}
private fun probeEnvironment() {
val minecraftEntry = mappings?.classEntries?.find {
it?.get("named") == "net/minecraft/client/MinecraftClient"
}
if (minecraftEntry == null) {
logger.error("Unable to probe environment. Please make sure you are using a valid environment.")
return
}
val officialName = minecraftEntry.get("official")?.replace('/', '.')
val intermediaryName = minecraftEntry.get("intermediary")?.replace('/', '.')
logger.info("Probing environment... (official: $officialName, intermediary: $intermediaryName)")
try {
Class.forName(officialName)
this.environment = "official"
logger.info("Official environment detected.")
} catch (_: ClassNotFoundException) {
try {
Class.forName(intermediaryName)
this.environment = "intermediary"
logger.info("Intermediary environment detected.")
return
} catch (_: ClassNotFoundException) {
logger.error("No matching environment detected. Please make sure you are using a valid environment.")
return
}
}
}
fun remapClassName(clazz: String): String {
if (environment == null) {
return clazz
}
val className = clazz.replace('.', '/')
return mappings?.classEntries?.find {
it?.get("named") == className
}?.get(environment)?.replace('/', '.') ?: clazz
}
fun remapField(clazz: Class<*>, name: String, superClasses: Boolean): String {
if (environment == null) {
return name
}
val classNames = mutableSetOf(clazz.name.replace('.', '/'))
if (superClasses) {
var current = clazz
while (current.name != "java.lang.Object") {
current = current.superclass
classNames.add(current.name.replace('.', '/'))
}
}
return mappings?.fieldEntries?.find {
val intern = it?.get(environment) ?: return@find false
val named = it.get("named") ?: return@find false
classNames.contains(intern.owner) && named.name == name
}?.get(environment)?.name ?: name
}
fun remapMethod(clazz: Class<*>, name: String, superClasses: Boolean): String {
if (environment == null) {
return name
}
val classNames = mutableSetOf(clazz.name.replace('.', '/'))
if (superClasses) {
var current = clazz
while (current.name != "java.lang.Object") {
current = current.superclass
classNames.add(current.name.replace('.', '/'))
}
}
return mappings?.methodEntries?.find {
val intern = it?.get(environment) ?: return@find false
val named = it.get("named") ?: return@find false
classNames.contains(intern.owner) && named.name == name
}?.get(environment)?.name ?: name
}
}

View File

@@ -6,10 +6,6 @@
"priority": 1337,
"mixinPriority": 1337,
"client": [
"graaljs.MixinHostClassDesc",
"graaljs.MixinHostClassLoader",
"graaljs.MixinHostContext",
"graaljs.MixinTruffleLanguage",
"minecraft.block.MixinAbstractBlock",
"minecraft.block.MixinBlock",
"minecraft.block.MixinBlockView",
@@ -88,7 +84,6 @@
"minecraft.render.MixinPostEffectPass",
"minecraft.render.MixinRenderTickCounter",
"minecraft.render.MixinSignText",
"sodium.MixinSodiumBlockOcclusionCache",
"minecraft.render.MixinTextRenderer",
"minecraft.render.MixinWorldRenderer",
"minecraft.render.entity.feature.MixinDeadmau5FeatureRenderer",
@@ -96,7 +91,12 @@
"minecraft.text.MixinChatHudLineVisible",
"minecraft.text.MixinTextColor",
"minecraft.text.MixinTranslatableTextContent",
"sodium.MixinSodiumLightDataAccessMixin"
"sodium.MixinSodiumBlockOcclusionCache",
"sodium.MixinSodiumLightDataAccessMixin",
"truffle.MixinHostClassDesc",
"truffle.MixinHostClassLoader",
"truffle.MixinHostContext",
"truffle.MixinTruffleLanguage"
],
"server": [],
"injectors": {