├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── hc-1.19.2-1.3.2.jar └── trove-1.0.2.jar ├── settings.gradle.kts └── src └── main ├── java └── ru │ └── hollowhorizon │ └── kotlinscript │ ├── KotlinScriptForForge.kt │ ├── common │ ├── events │ │ └── ScriptEvents.kt │ └── scripting │ │ ├── CompiledScript.kt │ │ ├── ScriptingCompiler.kt │ │ ├── kotlin │ │ ├── Annotations.kt │ │ ├── HollowScriptConfiguration.kt │ │ └── KJvmCompiledScriptFromJar.kt │ │ └── mappings │ │ ├── ASMRemapper.kt │ │ └── HollowMappings.kt │ └── mixin │ ├── BinaryJavaClassMixin.java │ ├── JvmCompilationUtilMixin.java │ ├── KotlinCoreEnvironmentMixin.java │ └── ScriptingJvmCompilerIsolatedMixin.java └── resources ├── META-INF └── mods.toml ├── kotlinscript.mixins.json ├── kotlinscript.png ├── mappings.nbt └── pack.mcmeta /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [ 1.19.2 ] 5 | pull_request: 6 | branches: [ 1.19.2 ] 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | java: [ 17 ] # Current Java LTS & minimum supported by Minecraft 13 | os: [ ubuntu-20.04 ] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - name: checkout repository 17 | uses: actions/checkout@v2 18 | - name: validate gradle wrapper 19 | uses: gradle/wrapper-validation-action@v1 20 | - name: setup jdk ${{ matrix.java }} 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: ${{ matrix.java }} 24 | - name: make gradle wrapper executable 25 | run: chmod +x ./gradlew 26 | - name: build 27 | run: ./gradlew build 28 | - name: capture build artifacts 29 | if: ${{ runner.os == 'Linux' && matrix.java == '17' }} 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: Artifacts 33 | path: build/libs/ 34 | - name: automatic upload 35 | uses: marvinpinto/action-automatic-releases@latest 36 | with: 37 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 38 | automatic_release_tag: "latest-1.19.2" 39 | prerelease: false 40 | title: "[AUTOMATIC BUILD 1.19.2]" 41 | files: | 42 | build/libs/*.jar 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | builded_hc/ 117 | logs/ 118 | 119 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 120 | !gradle-wrapper.jar 121 | 122 | !libs/trove-1.0.2.jar 123 | user.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 HollowHorizon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import net.minecraftforge.gradle.common.util.RunConfig 3 | import net.minecraftforge.gradle.userdev.UserDevExtension 4 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 5 | import org.spongepowered.asm.gradle.plugins.MixinExtension 6 | import java.time.ZonedDateTime 7 | import java.time.format.DateTimeFormatter 8 | import java.util.* 9 | 10 | val minecraft_version: String by project 11 | val forge_version: String by project 12 | val mod_id: String by project 13 | val mod_group: String by project 14 | val mod_version: String by project 15 | val mappings_version: String by project 16 | val mod_author: String by project 17 | 18 | val userConfig = Properties() 19 | val cfg = rootProject.file("user.properties") 20 | if (cfg.exists()) userConfig.load(cfg.inputStream()) 21 | 22 | buildscript { 23 | dependencies { 24 | classpath("org.spongepowered:mixingradle:0.7.38") 25 | } 26 | } 27 | 28 | plugins { 29 | id("net.minecraftforge.gradle") 30 | id("org.parchmentmc.librarian.forgegradle") 31 | id("org.jetbrains.kotlin.jvm") version "1.8.21" 32 | id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21" 33 | id("com.github.johnrengelman.shadow") version "8+" 34 | `maven-publish` 35 | } 36 | 37 | apply(plugin = "org.spongepowered.mixin") 38 | 39 | group = mod_group 40 | version = mod_version 41 | project.setProperty("archivesBaseName", mod_id) 42 | 43 | java { 44 | toolchain.languageVersion.set(JavaLanguageVersion.of(17)) 45 | } 46 | 47 | tasks.withType { 48 | kotlinOptions.jvmTarget = "17" 49 | kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn") 50 | } 51 | 52 | configure { 53 | mappings("parchment", "$mappings_version-$minecraft_version") 54 | 55 | accessTransformer("src/main/resources/META-INF/accesstransformer.cfg") 56 | 57 | val defaultConfig = Action { 58 | workingDirectory(project.file("run")) 59 | properties( 60 | mapOf( 61 | "forge.logging.markers" to "REGISTRIES", 62 | "forge.logging.console.level" to "debug" 63 | ) 64 | ) 65 | //jvmArg("-XX:+AllowEnhancedClassRedefinition") 66 | arg("-mixin.config=$mod_id.mixins.json") 67 | mods.create(mod_id) { 68 | source(the().sourceSets.getByName("main")) 69 | } 70 | } 71 | 72 | runs.create("client", defaultConfig) 73 | runs.create("server", defaultConfig) 74 | 75 | runs.all { 76 | lazyToken("minecraft_classpath") { 77 | library.copyRecursive().resolve().joinToString(File.pathSeparator) { it.absolutePath } 78 | } 79 | } 80 | } 81 | 82 | repositories { 83 | mavenCentral() 84 | maven { url = uri("https://jitpack.io") } 85 | maven { url = uri("https://cursemaven.com") } 86 | maven { url = uri("https://thedarkcolour.github.io/KotlinForForge/") } 87 | flatDir { dir("libs") } 88 | } 89 | 90 | val shadeKotlin by configurations.creating 91 | val library = configurations.create("library") 92 | 93 | configurations { 94 | library.extendsFrom(this["shadow"]) 95 | implementation.get().extendsFrom(library) 96 | compileOnly.get().extendsFrom(shadeKotlin) 97 | } 98 | 99 | dependencies { 100 | val minecraft = configurations["minecraft"] 101 | val shadow = configurations["shadow"] 102 | 103 | minecraft("net.minecraftforge:forge:$minecraft_version-$forge_version") 104 | 105 | annotationProcessor("org.spongepowered:mixin:0.8.5:processor") 106 | 107 | implementation("thedarkcolour:kotlinforforge:3.12.0") 108 | implementation(fg.deobf("ru.hollowhorizon:hc:1.19.2-1.3.2")) 109 | shadow("gnu.trove:trove:1.0.2") 110 | implementation(fg.deobf("curse.maven:embeddium-908741:4984830")) 111 | implementation(fg.deobf("curse.maven:oculus-581495:4763262")) 112 | implementation(fg.deobf("curse.maven:spark-361579:4505309")) 113 | 114 | val withoutKotlinStd: ExternalModuleDependency.() -> Unit = { 115 | exclude("gnu.trove", "trove") 116 | exclude("org.jetbrains.kotlin", "kotlin-stdlib") 117 | exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") 118 | exclude("org.jetbrains.kotlin", "kotlin-reflect") 119 | exclude("org.jetbrains.kotlin", "kotlinx-coroutines-core") 120 | exclude("org.jetbrains.kotlin", "kotlinx-coroutines-core-jvm") 121 | exclude("org.jetbrains.kotlin", "kotlinx-coroutines-jdk8") 122 | exclude("org.jetbrains.kotlin", "kotlinx-serialization-core") 123 | exclude("org.jetbrains.kotlin", "kotlinx-serialization-json") 124 | } 125 | shadow("org.jetbrains.kotlin:kotlin-scripting-jvm:1.8.21", withoutKotlinStd) 126 | shadow("org.jetbrains.kotlin:kotlin-scripting-jvm-host:1.8.21", withoutKotlinStd) 127 | shadow("org.jetbrains.kotlin:kotlin-script-runtime:1.8.21", withoutKotlinStd) 128 | shadow("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.21", withoutKotlinStd) 129 | shadow("org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.8.21", withoutKotlinStd) 130 | shadow("org.jetbrains:annotations:23.0.0") 131 | } 132 | 133 | if (System.getProperty("user.name").equals(userConfig.getProperty("user"))) { 134 | tasks.getByName("shadowJar").finalizedBy("copyJar") 135 | } 136 | 137 | fun Jar.createManifest() = manifest { 138 | attributes( 139 | "Automatic-Module-Name" to mod_id, 140 | "Specification-Title" to mod_id, 141 | "Specification-Vendor" to mod_author, 142 | "Specification-Version" to "1", 143 | "Implementation-Title" to project.name, 144 | "Implementation-Version" to version, 145 | "Implementation-Vendor" to mod_author, 146 | "Implementation-Timestamp" to ZonedDateTime.now() 147 | .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")), 148 | "MixinConfigs" to "$mod_id.mixins.json" 149 | ) 150 | } 151 | 152 | configure { 153 | add(sourceSets.main.get(), "$mod_id.refmap.json") 154 | } 155 | 156 | val jar = tasks.named("jar") { 157 | archiveClassifier.set("lite") 158 | exclude( 159 | "LICENSE.txt", "META-INF/MANIFSET.MF", "META-INF/maven/**", 160 | "META-INF/*.RSA", "META-INF/*.SF", "META-INF/versions/**" 161 | ) 162 | createManifest() 163 | finalizedBy("reobfJar") 164 | } 165 | 166 | val shadowJar = tasks.named("shadowJar") { 167 | archiveClassifier.set("") 168 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 169 | configurations = listOf(library, shadeKotlin) 170 | 171 | exclude( 172 | "LICENSE.txt", "META-INF/MANIFSET.MF", "META-INF/maven/**", 173 | "META-INF/*.RSA", "META-INF/*.SF", "META-INF/versions/**" 174 | ) 175 | 176 | dependencies { 177 | exclude(dependency("net.java.dev.jna:jna")) 178 | } 179 | 180 | relocate("org.jetbrains.kotlin.fir.analysis.native", "org.jetbrains.kotlin.fir.analysis.notnative") 181 | relocate( 182 | "org.jetbrains.kotlin.fir.analysis.diagnostics.native", 183 | "org.jetbrains.kotlin.fir.analysis.diagnostics.notnative" 184 | ) 185 | 186 | val packages = listOf( 187 | "gnu.trove" 188 | ) 189 | 190 | packages.forEach { relocate(it, "ru.hollowhorizon.repack.$it") } 191 | 192 | exclude("**/module-info.class") 193 | 194 | createManifest() 195 | 196 | finalizedBy("reobfShadowJar") 197 | } 198 | 199 | (extensions["reobf"] as NamedDomainObjectContainer<*>).create("shadowJar") 200 | tasks.getByName("build").dependsOn("shadowJar") 201 | 202 | tasks { 203 | whenTaskAdded { 204 | if (name == "prepareRuns") dependsOn(shadowJar) 205 | } 206 | } 207 | 208 | val copyJar by tasks.registering(Copy::class) { 209 | from(shadowJar.flatMap(Jar::getArchiveFile).get().asFile) 210 | into("../HollowEngine/hc") 211 | } 212 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx6G 2 | #kotlin.experimental.tryK2=true 3 | 4 | minecraft_version=1.19.2 5 | forge_version=43.3.7 6 | mappings_version=2022.11.27 7 | mod_id=kotlinscript 8 | mod_group=ru.hollowhorizon 9 | mod_version=1.4 10 | mod_author=hollowhorizon -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HollowHorizon/KotlinScriptForForge/ae85127331586b24fbb7ea3cd268dfdd4e5675e0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /libs/hc-1.19.2-1.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HollowHorizon/KotlinScriptForForge/ae85127331586b24fbb7ea3cd268dfdd4e5675e0/libs/hc-1.19.2-1.3.2.jar -------------------------------------------------------------------------------- /libs/trove-1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HollowHorizon/KotlinScriptForForge/ae85127331586b24fbb7ea3cd268dfdd4e5675e0/libs/trove-1.0.2.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | maven { url = uri("https://repo.spongepowered.org/repository/maven-public/") } 6 | maven { url = uri("https://maven.minecraftforge.net/") } 7 | maven { url = uri("https://maven.parchmentmc.org") } 8 | } 9 | plugins { 10 | id("net.minecraftforge.gradle") version "[6.0,6.2)" 11 | id("org.parchmentmc.librarian.forgegradle") version "1.+" 12 | } 13 | } 14 | 15 | plugins { 16 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 17 | } 18 | 19 | rootProject.name = "KotlinScriptForForge" -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/KotlinScriptForForge.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript 26 | 27 | import net.minecraftforge.fml.common.Mod 28 | import org.apache.logging.log4j.LogManager 29 | import org.apache.logging.log4j.Logger 30 | import ru.hollowhorizon.kotlinscript.common.scripting.ScriptingCompiler 31 | import ru.hollowhorizon.kotlinscript.common.scripting.kotlin.HollowScript 32 | 33 | 34 | @Mod(KotlinScriptForForge.MODID) 35 | object KotlinScriptForForge { 36 | val LOGGER: Logger = LogManager.getLogger() 37 | const val MODID: String = "kotlinscript" 38 | 39 | init { 40 | ScriptingCompiler.compileText( 41 | "ru.hollowhorizon.hc.HollowCore.LOGGER.info(\"KotlinScriptForForge successfully loaded!\")" 42 | ).execute() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/events/ScriptEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.events 26 | 27 | import net.minecraftforge.eventbus.api.Cancelable 28 | import net.minecraftforge.eventbus.api.Event 29 | import java.io.File 30 | 31 | open class ScriptEvent(val file: File) : Event() 32 | 33 | @Cancelable 34 | class ScriptErrorEvent(file: File, val type: ErrorType, val error: List) : ScriptEvent(file) 35 | 36 | class ScriptCompiledEvent(file: File) : ScriptEvent(file) 37 | class ScriptStartedEvent(file: File) : ScriptEvent(file) 38 | 39 | class ScriptError( 40 | val severity: Severity, 41 | val message: String, 42 | val source: String, 43 | val line: Int, 44 | val column: Int, 45 | val exception: Throwable? 46 | ) 47 | 48 | enum class Severity { 49 | DEBUG, INFO, WARNING, ERROR, FATAL 50 | } 51 | 52 | enum class ErrorType { 53 | COMPILATION_ERROR, RUNTIME_ERROR 54 | } -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/CompiledScript.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting 26 | 27 | import kotlinx.coroutines.runBlocking 28 | import net.minecraftforge.common.MinecraftForge 29 | import ru.hollowhorizon.kotlinscript.common.events.ScriptStartedEvent 30 | import ru.hollowhorizon.kotlinscript.common.scripting.ScriptingCompiler.saveScriptToJar 31 | import java.io.File 32 | import kotlin.script.experimental.api.* 33 | import kotlin.script.experimental.api.CompiledScript 34 | import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator 35 | import kotlin.script.experimental.jvm.impl.KJvmCompiledScript 36 | 37 | data class CompiledScript( 38 | val scriptName: String, 39 | val hash: String, 40 | val script: CompiledScript?, 41 | val scriptFile: File, 42 | ) { 43 | var errors: List? = null 44 | 45 | fun save(file: File) { 46 | if (script == null) return 47 | (script as KJvmCompiledScript).saveScriptToJar(file, hash) 48 | } 49 | 50 | fun execute(body: ScriptEvaluationConfiguration.Builder.() -> Unit = {}): ResultWithDiagnostics { 51 | if (script == null) { 52 | return ResultWithDiagnostics.Failure( 53 | arrayListOf( 54 | ScriptDiagnostic(-1, "Script not compiled!", ScriptDiagnostic.Severity.FATAL) 55 | ) 56 | ) 57 | } 58 | 59 | val evalConfig = ScriptEvaluationConfiguration { body() } 60 | val evaluator = BasicJvmScriptEvaluator() 61 | val result = runBlocking { 62 | evaluator(script, evalConfig) 63 | } 64 | 65 | 66 | if (result is ResultWithDiagnostics.Success) MinecraftForge.EVENT_BUS.post(ScriptStartedEvent(scriptFile)) 67 | 68 | 69 | return result 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/ScriptingCompiler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting 26 | 27 | import kotlinx.coroutines.runBlocking 28 | import net.minecraftforge.common.MinecraftForge 29 | import net.minecraftforge.fml.loading.FMLPaths 30 | import ru.hollowhorizon.kotlinscript.KotlinScriptForForge 31 | import ru.hollowhorizon.kotlinscript.common.events.* 32 | import ru.hollowhorizon.kotlinscript.common.scripting.kotlin.AbstractHollowScriptHost 33 | import ru.hollowhorizon.kotlinscript.common.scripting.kotlin.loadScriptFromJar 34 | import ru.hollowhorizon.kotlinscript.common.scripting.kotlin.loadScriptHashCode 35 | import java.io.ByteArrayOutputStream 36 | import java.io.File 37 | import java.io.FileOutputStream 38 | import java.io.PrintStream 39 | import java.util.jar.JarEntry 40 | import java.util.jar.JarOutputStream 41 | import java.util.jar.Manifest 42 | import kotlin.script.experimental.api.* 43 | import kotlin.script.experimental.host.FileScriptSource 44 | import kotlin.script.experimental.host.StringScriptSource 45 | import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate 46 | import kotlin.script.experimental.jvm.impl.* 47 | import kotlin.script.experimental.jvm.util.isError 48 | import kotlin.script.experimental.jvmhost.JvmScriptCompiler 49 | import kotlin.script.experimental.util.PropertiesCollection 50 | 51 | fun ResultWithDiagnostics.orException(): R = valueOr { 52 | throw IllegalStateException( 53 | it.errors().joinToString("\n"), 54 | it.reports.find { it.exception != null }?.exception 55 | ) 56 | } 57 | 58 | fun ResultWithDiagnostics.Failure.errors(): List = reports.map { diagnostic -> 59 | buildString { 60 | if (diagnostic.severity >= ScriptDiagnostic.Severity.WARNING) { 61 | append(diagnostic.message) 62 | 63 | if (diagnostic.sourcePath != null || diagnostic.location != null) { 64 | append(" at [") 65 | diagnostic.sourcePath?.let { append(it.substringAfterLast(File.separatorChar)) } 66 | diagnostic.location?.let { path -> 67 | append(':') 68 | append(path.start.line) 69 | append(':') 70 | append(path.start.col) 71 | } 72 | append("]") 73 | } 74 | if (diagnostic.exception != null) { 75 | append(": ") 76 | append(diagnostic.exception) 77 | ByteArrayOutputStream().use { os -> 78 | val ps = PrintStream(os) 79 | diagnostic.exception?.printStackTrace(ps) 80 | ps.flush() 81 | append("\n") 82 | append(os.toString()) 83 | } 84 | } 85 | } 86 | } 87 | }.filter { it.isNotEmpty() } 88 | 89 | object ScriptingCompiler { 90 | 91 | inline fun compileText(text: String): CompiledScript { 92 | val hostConfiguration = AbstractHollowScriptHost() 93 | 94 | val compilationConfiguration = createCompilationConfigurationFromTemplate( 95 | KotlinType(T::class), 96 | hostConfiguration, 97 | KotlinScriptForForge::class 98 | ) {} 99 | 100 | return runBlocking { 101 | val compiler = JvmScriptCompiler(hostConfiguration) 102 | val compiled = compiler(StringScriptSource(text), compilationConfiguration) 103 | 104 | return@runBlocking CompiledScript( 105 | "script.kts", "", 106 | compiled.valueOrNull(), FMLPaths.GAMEDIR.get().resolve("script.kts").toFile() 107 | ).apply { 108 | if (compiled.isError()) { 109 | this.errors = if (compiled.isError()) (compiled as ResultWithDiagnostics.Failure).errors() else null 110 | } 111 | 112 | } 113 | } 114 | } 115 | 116 | inline fun compileFile(script: File): CompiledScript { 117 | val hostConfiguration = AbstractHollowScriptHost() 118 | 119 | val compilationConfiguration = createCompilationConfigurationFromTemplate( 120 | KotlinType(T::class), 121 | hostConfiguration, 122 | KotlinScriptForForge::class 123 | ) {} 124 | 125 | return runBlocking { 126 | val compiledJar = script.parentFile.resolve(script.name + ".jar") 127 | val hashcode = script.readText().hashCode().toString() 128 | 129 | if (compiledJar.exists() && compiledJar.loadScriptHashCode() == hashcode) { 130 | return@runBlocking CompiledScript( 131 | script.name, hashcode, 132 | compiledJar.loadScriptFromJar(), script 133 | ) 134 | } 135 | 136 | val compiler = JvmScriptCompiler(hostConfiguration) 137 | val compiled = compiler(FileScriptSource(script), compilationConfiguration) 138 | 139 | return@runBlocking CompiledScript( 140 | script.name, hashcode, 141 | compiled.valueOrNull(), script 142 | ).apply { 143 | if (compiled.isError()) { 144 | val errors = compiled.reports.map { 145 | ScriptError( 146 | Severity.values()[it.severity.ordinal], 147 | it.message, 148 | it.sourcePath ?: "", 149 | it.location?.start?.line ?: 0, 150 | it.location?.start?.col ?: 0, 151 | it.exception 152 | ) 153 | } 154 | 155 | if (!MinecraftForge.EVENT_BUS.post(ScriptErrorEvent(script, ErrorType.COMPILATION_ERROR, errors))) { 156 | this.errors = 157 | if (compiled.isError()) (compiled as ResultWithDiagnostics.Failure).errors() else null 158 | } 159 | } else { 160 | save(compiledJar) 161 | MinecraftForge.EVENT_BUS.post(ScriptCompiledEvent(script)) 162 | } 163 | 164 | } 165 | } 166 | } 167 | 168 | fun shouldRecompile(script: File): Boolean { 169 | val compiledJar = script.parentFile.resolve(script.name + ".jar") 170 | return compiledJar.exists() && compiledJar.loadScriptHashCode() != script.readText().hashCode().toString() 171 | } 172 | 173 | fun KJvmCompiledScript.saveScriptToJar(outputJar: File, hash: String) { 174 | KotlinScriptForForge.LOGGER.info("saving script jar to: {}", outputJar.absolutePath) 175 | // Get the compiled module, which contains the output files 176 | val module = getCompiledModule().let { module -> 177 | // Ensure the module is of the correct type 178 | // (other types may be returned if the script is cached, for example, which is undesired) 179 | module as? KJvmCompiledModuleInMemory ?: throw IllegalArgumentException("Unsupported module type $module") 180 | } 181 | FileOutputStream(outputJar).use { fileStream -> 182 | // The compiled script jar manifest 183 | val manifest = Manifest().apply { 184 | mainAttributes.apply { 185 | putValue("Manifest-Version", "1.0") 186 | putValue("Created-By", "HollowCore ScriptingEngine") 187 | putValue("Script-Hashcode", hash) 188 | putValue("Main-Class", scriptClassFQName) 189 | } 190 | } 191 | 192 | // Create a new JarOutputStream for writing 193 | JarOutputStream(fileStream, manifest).use { jar -> 194 | // Write sanitized compiled script metadata 195 | jar.putNextEntry(JarEntry(scriptMetadataPath(scriptClassFQName))) 196 | jar.write(copyWithoutModule().apply(::shrinkSerializableScriptData).toBytes()) 197 | jar.closeEntry() 198 | 199 | // Write each output file 200 | module.compilerOutputFiles.forEach { (path, bytes) -> 201 | jar.putNextEntry(JarEntry(path)) 202 | jar.write(bytes) 203 | jar.closeEntry() 204 | } 205 | 206 | jar.finish() 207 | jar.flush() 208 | } 209 | fileStream.flush() 210 | } 211 | } 212 | } 213 | 214 | 215 | private fun shrinkSerializableScriptData(compiledScript: KJvmCompiledScript) { 216 | (compiledScript.compilationConfiguration.entries() as? MutableSet, Any?>>) 217 | ?.removeIf { it.key == ScriptCompilationConfiguration.dependencies || it.key == ScriptCompilationConfiguration.defaultImports } 218 | } -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/kotlin/Annotations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting.kotlin 26 | 27 | @Target(AnnotationTarget.FILE) 28 | @Repeatable 29 | @Retention(AnnotationRetention.SOURCE) 30 | annotation class Import(vararg val paths: String) -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/kotlin/HollowScriptConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting.kotlin 26 | 27 | import cpw.mods.modlauncher.TransformingClassLoader 28 | import net.minecraftforge.fml.ModList 29 | import net.minecraftforge.fml.loading.FMLLoader 30 | import ru.hollowhorizon.kotlinscript.KotlinScriptForForge 31 | import java.io.File 32 | import kotlin.io.path.absolutePathString 33 | import kotlin.script.experimental.annotations.KotlinScript 34 | import kotlin.script.experimental.api.* 35 | import kotlin.script.experimental.host.FileBasedScriptSource 36 | import kotlin.script.experimental.host.FileScriptSource 37 | import kotlin.script.experimental.host.ScriptingHostConfiguration 38 | import kotlin.script.experimental.host.getScriptingClass 39 | import kotlin.script.experimental.jvm.* 40 | import kotlin.script.experimental.jvm.util.classpathFromClassloader 41 | 42 | class HollowScriptConfiguration : AbstractHollowScriptConfiguration({}) 43 | 44 | @KotlinScript( 45 | "HollowScript", "ks.kts", compilationConfiguration = HollowScriptConfiguration::class 46 | ) 47 | abstract class HollowScript 48 | 49 | class AbstractHollowScriptHost : ScriptingHostConfiguration({ 50 | getScriptingClass(JvmGetScriptingClass()) 51 | classpathFromClassloader(TransformingClassLoader.getSystemClassLoader()) 52 | }) 53 | 54 | abstract class AbstractHollowScriptConfiguration(body: Builder.() -> Unit) : ScriptCompilationConfiguration({ 55 | body() 56 | 57 | jvm { 58 | compilerOptions( 59 | "-opt-in=kotlin.time.ExperimentalTime,kotlin.ExperimentalStdlibApi", 60 | "-jvm-target=17", 61 | "-Xadd-modules=ALL-MODULE-PATH" //Loading kotlin from shadowed jar 62 | ) 63 | 64 | //Скорее всего в этом случае этот класс был загружен через IDE, поэтому получить моды и classpath автоматически нельзя 65 | if (!FMLLoader.isProduction() && FMLLoader.launcherHandlerName() == null) { 66 | dependenciesFromCurrentContext(wholeClasspath = true) 67 | return@jvm 68 | } 69 | 70 | val stdLib = ModList.get().getModFileById(KotlinScriptForForge.MODID).file.filePath.toFile().absolutePath 71 | System.setProperty("kotlin.java.stdlib.jar", stdLib) 72 | 73 | val files = HashSet() 74 | 75 | files.addAll(ModList.get().mods.map { File(it.owningFile.file.filePath.absolutePathString()) }) 76 | FMLLoader.getGamePath().resolve("mods").toFile().listFiles()?.forEach(files::add) 77 | files.addAll( 78 | FMLLoader.getLaunchHandler().minecraftPaths.otherModPaths.flatten().map { File(it.absolutePathString()) }) 79 | files.addAll(FMLLoader.getLaunchHandler().minecraftPaths.otherArtifacts.map { File(it.absolutePathString()) }) 80 | files.addAll(FMLLoader.getLaunchHandler().minecraftPaths.minecraftPaths.map { File(it.absolutePathString()) }) 81 | 82 | if (FMLLoader.getDist().isDedicatedServer) { 83 | listFilesRecursively(FMLLoader.getGamePath().resolve("libraries").toFile(), files) 84 | } 85 | 86 | dependenciesFromClassContext(HollowScriptConfiguration::class, wholeClasspath = true) 87 | 88 | files.removeIf { it.isDirectory } 89 | updateClasspath(files.distinct().sortedBy { it.absolutePath }.onEach { KotlinScriptForForge.LOGGER.info(it.absolutePath) }) 90 | } 91 | 92 | defaultImports( 93 | Import::class 94 | ) 95 | 96 | refineConfiguration { 97 | onAnnotations(Import::class, handler = HollowScriptConfigurator()) 98 | } 99 | 100 | ide { acceptedLocations(ScriptAcceptedLocation.Everywhere) } 101 | }) 102 | 103 | private fun listFilesRecursively(directory: File, fileList: HashSet) { 104 | directory.listFiles()?.forEach { 105 | if (it.isDirectory) listFilesRecursively(it, fileList) 106 | else fileList.add(it) 107 | } 108 | } 109 | 110 | class HollowScriptConfigurator : RefineScriptCompilationConfigurationHandler { 111 | override operator fun invoke(context: ScriptConfigurationRefinementContext) = processAnnotations(context) 112 | 113 | private fun processAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { 114 | val annotations = context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() } 115 | ?: return context.compilationConfiguration.asSuccess() 116 | 117 | val scriptBaseDir = (context.script as? FileBasedScriptSource)?.file?.parentFile 118 | 119 | val importedSources = annotations.flatMap { 120 | (it as? Import)?.paths?.map { sourceName -> 121 | FileScriptSource(scriptBaseDir?.resolve(sourceName) ?: File(sourceName)) 122 | } ?: emptyList() 123 | } 124 | 125 | return ScriptCompilationConfiguration(context.compilationConfiguration) { 126 | if (importedSources.isNotEmpty()) importScripts.append(importedSources) 127 | }.asSuccess() 128 | } 129 | } -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/kotlin/KJvmCompiledScriptFromJar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting.kotlin 26 | 27 | import java.io.File 28 | import java.io.InputStream 29 | import java.security.ProtectionDomain 30 | import java.util.* 31 | import java.util.jar.JarEntry 32 | import java.util.jar.JarFile 33 | import java.util.jar.JarInputStream 34 | import kotlin.reflect.KClass 35 | import kotlin.script.experimental.api.* 36 | import kotlin.script.experimental.jvm.baseClassLoader 37 | import kotlin.script.experimental.jvm.impl.KJvmCompiledScript 38 | import kotlin.script.experimental.jvm.impl.createScriptFromClassLoader 39 | import kotlin.script.experimental.jvm.jvm 40 | 41 | fun File.loadScriptHashCode() = inputStream().use { istream -> 42 | JarInputStream(istream).use { 43 | it.manifest.mainAttributes.getValue("Script-Hashcode") 44 | ?: throw IllegalArgumentException("No Script-Hashcode manifest attribute") 45 | } 46 | } 47 | 48 | 49 | fun File.loadScriptFromJar(): CompiledScript { 50 | val className = inputStream().use { istream -> 51 | JarInputStream(istream).use { 52 | it.manifest.mainAttributes.getValue("Main-Class") 53 | ?: throw IllegalArgumentException("No Main-Class manifest attribute") 54 | } 55 | } 56 | return KJvmCompiledScriptFromJar(className, this) 57 | } 58 | 59 | 60 | internal class KJvmCompiledScriptFromJar(private val scriptClassFQName: String, private val file: File) : 61 | CompiledScript { 62 | private var loadedScript: KJvmCompiledScript? = null 63 | 64 | private fun getScriptOrFail(): KJvmCompiledScript = 65 | loadedScript ?: throw RuntimeException("Compiled script is not loaded yet") 66 | 67 | override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics> { 68 | if (loadedScript == null) { 69 | val actualEvalConfig = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration() 70 | val baseClassLoader = actualEvalConfig[ScriptEvaluationConfiguration.jvm.baseClassLoader] 71 | ?: Thread.currentThread().contextClassLoader 72 | val classLoader = createScriptMemoryClassLoader(baseClassLoader) 73 | loadedScript = createScriptFromClassLoader(scriptClassFQName, classLoader) 74 | } 75 | return getScriptOrFail().getClass(scriptEvaluationConfiguration) 76 | } 77 | 78 | override val compilationConfiguration: ScriptCompilationConfiguration 79 | get() = getScriptOrFail().compilationConfiguration 80 | 81 | override val sourceLocationId: String? 82 | get() = loadedScript?.sourceLocationId 83 | 84 | override val otherScripts: List 85 | get() = getScriptOrFail().otherScripts 86 | 87 | override val resultField: Pair? 88 | get() = getScriptOrFail().resultField 89 | 90 | private fun createScriptMemoryClassLoader(parent: ClassLoader?): ClassLoader { 91 | val file = JarFile(file) 92 | val entries = file.entries().asSequence().associate { it.name to file.getInputStream(it).readBytes() } 93 | return MemoryClassLoader(entries, parent) 94 | } 95 | 96 | private fun JarInputStream.readEntries(): Map { 97 | return generateSequence(::getNextJarEntry) 98 | .associate { Pair(it.name, readBytes()) } 99 | } 100 | 101 | private fun Enumeration.nextElementOrNull() = if (this.hasMoreElements()) this.nextElement() else null 102 | } 103 | 104 | internal class MemoryClassLoader(private val resources: Map, parent: ClassLoader?) : 105 | ClassLoader(parent) { 106 | override fun findClass(name: String): Class<*> { 107 | val resource = name.replace('.', '/') + ".class" 108 | 109 | return resources[resource]?.let { bytes -> 110 | val protectionDomain = ProtectionDomain(null, null) 111 | return defineClass(name, bytes, 0, bytes.size, protectionDomain) 112 | } 113 | ?: throw ClassNotFoundException(name) 114 | } 115 | 116 | override fun getResourceAsStream(name: String): InputStream? { 117 | return resources[name]?.inputStream() 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/mappings/ASMRemapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting.mappings 26 | 27 | import cpw.mods.modlauncher.TransformingClassLoader 28 | import org.objectweb.asm.ClassReader 29 | import org.objectweb.asm.ClassWriter 30 | import org.objectweb.asm.tree.* 31 | import ru.hollowhorizon.kotlinscript.KotlinScriptForForge 32 | import java.io.ByteArrayInputStream 33 | 34 | object ASMRemapper { 35 | @JvmField 36 | val CLASS_CACHE = HashMap() 37 | 38 | fun remap(bytes: ByteArray): ByteArray { 39 | val node = bytes.readClass() 40 | 41 | remapMethods(node) 42 | remapFields(node) 43 | 44 | return node.writeClass() 45 | } 46 | 47 | private fun remapMethods(node: ClassNode) { 48 | node.methods.forEach { method -> 49 | remapMethod(node, method) 50 | 51 | method.instructions.forEach { insn -> 52 | when (insn) { 53 | is FieldInsnNode -> { 54 | val owner = insn.owner.node 55 | if (owner != null) remapFieldInsn(owner, insn) 56 | } 57 | 58 | is MethodInsnNode -> { 59 | val owner = insn.owner.node 60 | 61 | if (owner != null) remapMethodInsn(owner, insn) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | private fun remapFieldInsn(node: ClassNode, field: FieldInsnNode) { 69 | field.name = HollowMappings.MAPPINGS.fieldObf(node, field.name) 70 | } 71 | 72 | private fun remapMethod(node: ClassNode, method: MethodNode) { 73 | method.name = HollowMappings.MAPPINGS.methodObf(node, method.name, method.desc) 74 | } 75 | 76 | private fun remapMethodInsn(node: ClassNode, method: MethodInsnNode) { 77 | method.name = HollowMappings.MAPPINGS.methodObf(node, method.name, method.desc) 78 | } 79 | 80 | private fun remapFields(node: ClassNode) { 81 | node.fields.forEach { method -> 82 | remapField(node, method) 83 | } 84 | } 85 | 86 | private fun remapField(node: ClassNode, field: FieldNode) { 87 | field.name = HollowMappings.MAPPINGS.fieldObf(node, field.name) 88 | } 89 | 90 | 91 | } 92 | 93 | fun ByteArray.readClass(): ClassNode { 94 | val classNode = ClassNode() 95 | val classReader = ClassReader(this) 96 | classReader.accept(classNode, 0) 97 | return classNode 98 | } 99 | 100 | fun ClassNode.writeClass(): ByteArray { 101 | val classWriter = ClassWriter(0) 102 | this.accept(classWriter) 103 | return classWriter.toByteArray() 104 | } 105 | 106 | val String.node: ClassNode? 107 | get() { 108 | return try { 109 | val node = ClassNode() 110 | node.apply { 111 | val clazz = this@node.replace(".", "/") + ".class" 112 | val stream = 113 | if (ASMRemapper.CLASS_CACHE.containsKey(clazz)) ByteArrayInputStream(ASMRemapper.CLASS_CACHE[clazz]) 114 | else TransformingClassLoader.getSystemClassLoader().getResourceAsStream(clazz) ?: Class.forName(this@node.replace("/", ".")).classLoader.getResourceAsStream(clazz) 115 | 116 | if (stream == null) KotlinScriptForForge.LOGGER.warn( 117 | "Class {} not found! It may be classLoader: {}", 118 | this@node.replace("/", "."), 119 | Class.forName(this@node.replace("/", ".")).classLoader.name 120 | ) 121 | ClassReader(stream).accept(this, 0) 122 | stream?.close() 123 | } 124 | } catch (e: Exception) { 125 | KotlinScriptForForge.LOGGER.error("error, not found: {}", this, e) 126 | null 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/common/scripting/mappings/HollowMappings.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.common.scripting.mappings 26 | 27 | 28 | import kotlinx.serialization.Serializable 29 | import org.objectweb.asm.tree.ClassNode 30 | import ru.hollowhorizon.hc.client.utils.nbt.* 31 | import java.io.DataInputStream 32 | import java.io.File 33 | import java.io.InputStream 34 | 35 | object HollowMappings { 36 | @JvmField 37 | val MAPPINGS = MAPPINGS_SERIALIZER.deserialize(run { 38 | val stream = (HollowMappings.javaClass.getResourceAsStream("/mappings.nbt") 39 | ?: throw IllegalStateException("Mappings file not found!")) 40 | 41 | return@run DataInputStream(stream).loadAsNBT() 42 | } 43 | ) 44 | } 45 | 46 | fun main() { 47 | val mapping = Mappings.loadFromTSRG(Mappings.Companion::class.java.getResourceAsStream("/output.tsrg")!!) 48 | 49 | NBTFormat.serialize(mapping).save(File("mappings.nbt").outputStream()) 50 | 51 | println(mapping) 52 | } 53 | 54 | @Serializable 55 | data class Mappings(val mappings: HashSet) { 56 | companion object { 57 | fun loadFromTSRG(stream: InputStream): Mappings { 58 | val mappings = HashSet() 59 | var lastClass: ClassMapping? = null 60 | stream.bufferedReader().forEachLine { line -> 61 | if (!line.startsWith("\t")) { //Если идёт класс 62 | val data = line.replace("/", ".").replace("$", ".").split(" ") 63 | mappings.add(ClassMapping(data[0], data[1]).apply { lastClass = this }) 64 | } else if (!line.startsWith("\t\t")) { //Если идёт метод или параметр 65 | val data = line.substringAfter("\t").replace("/", ".").replace("$", ".").split(" ") 66 | if (data.size == 3) lastClass?.methods?.add(MethodMapping(data[0], data[2], data[1])) 67 | else lastClass?.fields?.add(FieldMapping(data[0], data[1])) 68 | } 69 | } 70 | stream.close() 71 | return Mappings(mappings) 72 | } 73 | } 74 | 75 | @kotlinx.serialization.Transient 76 | val fields = mappings.flatMap { it.fields.map { f -> f.srgName to f.mcpName } }.toMap() 77 | 78 | @kotlinx.serialization.Transient 79 | val methods = mappings.flatMap { it.methods.map { m -> m.srgName to m.mcpName } }.toMap() 80 | 81 | operator fun get(node: ClassNode) = mappings.find { it.mcpName == node.name.replace("/", ".").replace("$", ".") } 82 | 83 | fun fieldObf(node: ClassNode, name: String): String { 84 | val newName = this[node]?.fieldObf(name) ?: name 85 | 86 | if (newName == name) { 87 | (node.interfaces + node.superName).filterNotNull().mapNotNull { it.node }.forEach { supNode -> 88 | val fName = fieldObf(supNode, name) 89 | if (fName != name) return fName 90 | } 91 | } 92 | return newName 93 | } 94 | 95 | fun methodObf(node: ClassNode, name: String, signature: String): String { 96 | val newName = this[node]?.methodObf(name, signature) ?: name 97 | 98 | if (newName == name) { 99 | (node.interfaces + node.superName).filterNotNull().mapNotNull { it.node }.forEach { supNode -> 100 | val fName = methodObf(supNode, name, signature) 101 | if (fName != name) return fName 102 | } 103 | } 104 | return newName 105 | } 106 | 107 | fun fieldDeobf(name: String) = this.fields.getOrDefault(name, name) 108 | fun methodDeobf(name: String) = this.methods.getOrDefault(name, name) 109 | } 110 | 111 | @Serializable 112 | data class ClassMapping( 113 | val mcpName: String, 114 | val srgName: String, 115 | ) { 116 | val fields = HashSet() 117 | val methods = HashSet() 118 | 119 | fun fieldObf(text: String): String { 120 | return fields.find { it.mcpName == text }?.srgName ?: text 121 | } 122 | 123 | fun methodObf(deobfName: String, signature: String): String { 124 | return methods.firstOrNull { 125 | it.mcpName == deobfName && it.params == signature.replace("/", ".").replace("$", ".") 126 | }?.srgName ?: deobfName 127 | } 128 | } 129 | 130 | @Serializable 131 | data class FieldMapping( 132 | val mcpName: String, 133 | val srgName: String, 134 | ) 135 | 136 | @Serializable 137 | data class MethodMapping( 138 | val mcpName: String, 139 | val srgName: String, 140 | val params: String, 141 | ) -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/mixin/BinaryJavaClassMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.mixin; 26 | 27 | import net.minecraftforge.fml.loading.FMLEnvironment; 28 | import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass; 29 | import org.spongepowered.asm.mixin.Mixin; 30 | import org.spongepowered.asm.mixin.injection.At; 31 | import org.spongepowered.asm.mixin.injection.ModifyArg; 32 | import ru.hollowhorizon.kotlinscript.common.scripting.mappings.HollowMappings; 33 | 34 | @Mixin(value = BinaryJavaClass.class, remap = false) 35 | public abstract class BinaryJavaClassMixin { 36 | 37 | @ModifyArg(method = "visitMethod", at = @At(value = "INVOKE", target = "Lorg/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaMethodBase$Companion;create(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Lorg/jetbrains/kotlin/load/java/structure/JavaClass;Lorg/jetbrains/kotlin/load/java/structure/impl/classFiles/ClassifierResolutionContext;Lorg/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryClassSignatureParser;)Lkotlin/Pair;"), index = 0) 38 | private String onMethodCreating(String name) { 39 | if (!FMLEnvironment.production) return name; 40 | return name.startsWith("m_") ? HollowMappings.MAPPINGS.methodDeobf(name) : name; 41 | } 42 | 43 | @ModifyArg(method = "visitField", at = @At(value = "INVOKE", target = "Lorg/jetbrains/kotlin/name/Name;identifier(Ljava/lang/String;)Lorg/jetbrains/kotlin/name/Name;"), index = 0) 44 | private String onFieldCreating(String name) { 45 | if (!FMLEnvironment.production) return name; 46 | return name.startsWith("f_") ? HollowMappings.MAPPINGS.fieldDeobf(name) : name; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/mixin/JvmCompilationUtilMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.mixin; 26 | 27 | import net.minecraftforge.fml.loading.FMLEnvironment; 28 | import org.jetbrains.kotlin.backend.common.output.OutputFile; 29 | import org.jetbrains.kotlin.codegen.state.GenerationState; 30 | import org.jetbrains.kotlin.scripting.compiler.plugin.impl.JvmCompilationUtilKt; 31 | import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmCompiledModuleInMemoryImpl; 32 | import org.spongepowered.asm.mixin.Mixin; 33 | import org.spongepowered.asm.mixin.injection.At; 34 | import org.spongepowered.asm.mixin.injection.Inject; 35 | import org.spongepowered.asm.mixin.injection.Redirect; 36 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 37 | import ru.hollowhorizon.kotlinscript.common.scripting.mappings.ASMRemapper; 38 | 39 | @Mixin(value = JvmCompilationUtilKt.class, remap = false) 40 | public class JvmCompilationUtilMixin { 41 | 42 | @Inject(method = "makeCompiledModule", at = @At("HEAD")) 43 | private static void makeCacheForClassLoading(GenerationState generationState, CallbackInfoReturnable cir) { 44 | if (!FMLEnvironment.production) return; 45 | generationState.getFactory().asList().forEach(file -> ASMRemapper.CLASS_CACHE.put(file.getRelativePath(), file.asByteArray())); 46 | } 47 | 48 | @Inject(method = "makeCompiledModule", at = @At("TAIL")) 49 | private static void clearCacheForClassLoading(GenerationState generationState, CallbackInfoReturnable cir) { 50 | if (!FMLEnvironment.production) return; 51 | ASMRemapper.CLASS_CACHE.clear(); 52 | } 53 | 54 | @Redirect(method = "makeCompiledModule", at = @At(value = "INVOKE", target = "Lorg/jetbrains/kotlin/backend/common/output/OutputFile;asByteArray()[B")) 55 | private static byte[] makeCompiledModule(OutputFile instance) { 56 | if (!instance.getRelativePath().endsWith(".class") || !FMLEnvironment.production) return instance.asByteArray(); 57 | return ASMRemapper.INSTANCE.remap(instance.asByteArray()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/mixin/KotlinCoreEnvironmentMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.mixin; 26 | 27 | import net.minecraftforge.fml.ModList; 28 | import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; 29 | import org.spongepowered.asm.mixin.Mixin; 30 | import org.spongepowered.asm.mixin.injection.At; 31 | import org.spongepowered.asm.mixin.injection.Redirect; 32 | import ru.hollowhorizon.kotlinscript.KotlinScriptForForge; 33 | 34 | import java.io.File; 35 | 36 | @Mixin(KotlinCoreEnvironment.Companion.class) 37 | public class KotlinCoreEnvironmentMixin { 38 | 39 | /** 40 | * Specify the correct path to the compiler configuration (It is embedded in HollowCore) 41 | */ 42 | @Redirect(method = "registerApplicationExtensionPointsAndExtensionsFrom", at = @At(value = "INVOKE", target = "Lorg/jetbrains/kotlin/utils/PathUtil;getResourcePathForClass(Ljava/lang/Class;)Ljava/io/File;"), remap = false) 43 | private File getResourcePathForClass(Class aClass) { 44 | return ModList.get().getModFileById(KotlinScriptForForge.MODID).getFile().getFilePath().toFile(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ru/hollowhorizon/kotlinscript/mixin/ScriptingJvmCompilerIsolatedMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 HollowHorizon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package ru.hollowhorizon.kotlinscript.mixin; 26 | 27 | import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptJvmCompilerImplsKt; 28 | import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptJvmCompilerIsolated; 29 | import org.spongepowered.asm.mixin.Mixin; 30 | import org.spongepowered.asm.mixin.injection.Inject; 31 | 32 | @Mixin(ScriptJvmCompilerImplsKt.class) 33 | public class ScriptingJvmCompilerIsolatedMixin { 34 | //@Inject() 35 | private void onCompile() { 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "kotlinforforge" 2 | loaderVersion = "[3,)" 3 | license = "MIT License" 4 | logoFile="kotlinscript.png" 5 | [[mods]] 6 | modId = "kotlinscript" 7 | version = "1.4" 8 | displayName = "KotlinScript For Forge" 9 | displayURL = "https://github.com/HollowHorizon/" 10 | authors = "HolloHorizon" 11 | description = ''' 12 | Mod that provides tools for compiling kotlin scripts at runtime. 13 | ''' 14 | [[dependencies.kotlinscript]] 15 | modId = "forge" 16 | mandatory = true 17 | versionRange = "[40,44.0.0)" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | [[dependencies.kotlinscript]] 21 | modId = "minecraft" 22 | mandatory = true 23 | versionRange = "[1.18,1.20)" 24 | ordering = "NONE" 25 | side = "BOTH" 26 | [[dependencies.kotlinscript]] 27 | modId="hc" 28 | mandatory=true 29 | versionRange="[1.3.2,)" 30 | ordering = "AFTER" 31 | side = "BOTH" 32 | [[dependencies.kotlinscript]] 33 | modId="kotlinforforge" 34 | mandatory=true 35 | versionRange="[3.10.0,)" 36 | ordering = "AFTER" 37 | side = "BOTH" 38 | -------------------------------------------------------------------------------- /src/main/resources/kotlinscript.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "ru.hollowhorizon.kotlinscript.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "refmap": "kotlinscript.refmap.json", 7 | "mixins": [ 8 | "BinaryJavaClassMixin", 9 | "JvmCompilationUtilMixin", 10 | "KotlinCoreEnvironmentMixin", 11 | "ScriptingJvmCompilerIsolatedMixin" 12 | ], 13 | "client": [ 14 | ], 15 | "injectors": { 16 | "defaultRequire": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/kotlinscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HollowHorizon/KotlinScriptForForge/ae85127331586b24fbb7ea3cd268dfdd4e5675e0/src/main/resources/kotlinscript.png -------------------------------------------------------------------------------- /src/main/resources/mappings.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HollowHorizon/KotlinScriptForForge/ae85127331586b24fbb7ea3cd268dfdd4e5675e0/src/main/resources/mappings.nbt -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "kotlinscript resources", 4 | "pack_format": 10, 5 | "forge:resource_pack_format": 9, 6 | "forge:data_pack_format": 10 7 | } 8 | } 9 | --------------------------------------------------------------------------------